Cascade delete attachments properly.

Cascade delete replied to topic events for replies properly.
Cascade delete likes on topic posts properly.
Cascade delete replies and their children properly.
Recalculate user stats properly when items are deleted.
Users can now unlike topic opening posts.
Add a recalculator to fix abnormalities across upgrades.
Try fixing a last_ip daily update bug.

Add Existable interface.
Add Delete method to LikeStore.
Add Each, Exists, Create, CountUser, CountMegaUser and CountBigUser methods to ReplyStore.
Add CountUser, CountMegaUser, CountBigUser methods to TopicStore.
Add Each method to UserStore.
Add Add, Delete and DeleteResource methods to SubscriptionStore.
Add Delete, DeleteByParams, DeleteByParamsExtra and AidsByParamsExtra methods to ActivityStream.
Add Exists method to ProfileReplyStore.

Add DropColumn, RenameColumn and ChangeColumn to the database adapters.

Shorten ipaddress column names to ip.
- topics table.
- replies table
- users_replies table.
- polls_votes table.
Add extra column to activity_stream table.
Fix an issue upgrading sites to MariaDB 10.3 from older versions of Gosora. Please report any other issues you find.

You need to run the updater / patcher for this commit.
This commit is contained in:
Azareal 2020-01-31 17:22:08 +10:00
parent e1702687f7
commit 6935637867
75 changed files with 1488 additions and 600 deletions

View File

@ -197,7 +197,7 @@ func setupData(client *elastic.Client) error {
}
oi := 0
err := qgen.NewAcc().Select("topics").Cols("tid, title, content, createdBy, ipaddress").Each(func(rows *sql.Rows) error {
err := qgen.NewAcc().Select("topics").Cols("tid,title,content,createdBy,ip").Each(func(rows *sql.Rows) error {
t := ESTopic{}
err := rows.Scan(&t.ID, &t.Title, &t.Content, &t.CreatedBy, &t.IP)
if err != nil {
@ -233,7 +233,7 @@ func setupData(client *elastic.Client) error {
rf(rin[i])
}
oi := 0
err := qgen.NewAcc().Select("replies").Cols("rid, tid, content, createdBy, ipaddress").Each(func(rows *sql.Rows) error {
err := qgen.NewAcc().Select("replies").Cols("rid,tid,content,createdBy,ip").Each(func(rows *sql.Rows) error {
r := ESReply{}
err := rows.Scan(&r.ID, &r.TID, &r.Content, &r.CreatedBy, &r.IP)
if err != nil {

View File

@ -204,9 +204,9 @@ func seedTables(a qgen.Adapter) error {
//
qgen.Install.SimpleInsert("topics", "title, content, parsed_content, createdAt, lastReplyAt, lastReplyBy, createdBy, parentID, ipaddress", "'Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2,'::1'")
qgen.Install.SimpleInsert("topics", "title, content, parsed_content, createdAt, lastReplyAt, lastReplyBy, createdBy, parentID, ip", "'Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2,'::1'")
qgen.Install.SimpleInsert("replies", "tid, content, parsed_content, createdAt, createdBy, lastUpdated, lastEdit, lastEditBy, ipaddress", "1,'A reply!','A reply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0,'::1'")
qgen.Install.SimpleInsert("replies", "tid, content, parsed_content, createdAt, createdBy, lastUpdated, lastEdit, lastEditBy, ip", "1,'A reply!','A reply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0,'::1'")
qgen.Install.SimpleInsert("menus", "", "")

View File

@ -39,6 +39,7 @@ func createTables(adapter qgen.Adapter) (err error) {
// TODO: Drop these columns?
tC{"url_prefix", "varchar", 20, false, false, "''"},
tC{"url_name", "varchar", 100, false, false, "''"},
//tC{"pub_key", "text", 0, false, false, "''"},
tC{"level", "smallint", 0, false, false, "0"},
tC{"score", "int", 0, false, false, "0"},
@ -253,7 +254,7 @@ func createTables(adapter qgen.Adapter) (err error) {
tC{"sticky", "boolean", 0, false, false, "0"},
// TODO: Add an index for this
tC{"parentID", "int", 0, false, false, "2"},
tC{"ipaddress", "varchar", 200, false, false, "0.0.0.0.0"},
tC{"ip", "varchar", 200, false, false, "''"},
tC{"postCount", "int", 0, false, false, "1"},
tC{"likeCount", "int", 0, false, false, "0"},
tC{"attachCount", "int", 0, false, false, "0"},
@ -286,7 +287,7 @@ func createTables(adapter qgen.Adapter) (err error) {
tC{"lastEdit", "int", 0, false, false, "0"},
tC{"lastEditBy", "int", 0, false, false, "0"},
tC{"lastUpdated", "datetime", 0, false, false, ""},
tC{"ipaddress", "varchar", 200, false, false, "0.0.0.0.0"},
tC{"ip", "varchar", 200, false, false, "''"},
tC{"likeCount", "int", 0, false, false, "0"},
tC{"attachCount", "int", 0, false, false, "0"},
tC{"words", "int", 0, false, false, "1"}, // ? - replies has a default of 1 and topics has 0? why?
@ -357,7 +358,7 @@ func createTables(adapter qgen.Adapter) (err error) {
tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"option", "int", 0, false, false, "0"},
tC{"castAt", "createdAt", 0, false, false, ""},
tC{"ipaddress", "varchar", 200, false, false, "0.0.0.0.0"},
tC{"ip", "varchar", 200, false, false, "''"},
}, nil,
)
@ -371,7 +372,7 @@ func createTables(adapter qgen.Adapter) (err error) {
tC{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"lastEdit", "int", 0, false, false, "0"},
tC{"lastEditBy", "int", 0, false, false, "0"},
tC{"ipaddress", "varchar", 200, false, false, "0.0.0.0.0"},
tC{"ip", "varchar", 200, false, false, "''"},
},
[]tblKey{
tblKey{"rid", "primary", "", false},
@ -464,6 +465,7 @@ func createTables(adapter qgen.Adapter) (err error) {
tC{"elementType", "varchar", 50, false, false, ""}, /* topic, post (calling it post here to differentiate it from the 'reply' event), forum, user */
tC{"elementID", "int", 0, false, false, ""}, /* the ID of the element being acted upon */
tC{"createdAt", "createdAt", 0, false, false, ""},
tC{"extra", "varchar", 200, false, false, "''"},
},
[]tblKey{
tblKey{"asid", "primary", "", false},

View File

@ -1,47 +1,92 @@
package common
import "database/sql"
import "github.com/Azareal/Gosora/query_gen"
import (
"database/sql"
qgen "github.com/Azareal/Gosora/query_gen"
)
var Activity ActivityStream
type ActivityStream interface {
Add(a Alert) (int, error)
Get(id int) (Alert, error)
Delete(id int) error
DeleteByParams(event string, targetID int, targetType string) error
DeleteByParamsExtra(event string, targetID int, targetType, extra string) error
AidsByParamsExtra(event string, elementID int, elementType, extra string) (aids []int, err error)
Count() (count int)
}
type DefaultActivityStream struct {
add *sql.Stmt
get *sql.Stmt
delete *sql.Stmt
deleteByParams *sql.Stmt
deleteByParamsExtra *sql.Stmt
aidsByParamsExtra *sql.Stmt
count *sql.Stmt
}
func NewDefaultActivityStream(acc *qgen.Accumulator) (*DefaultActivityStream, error) {
as := "activity_stream"
return &DefaultActivityStream{
add: acc.Insert(as).Columns("actor, targetUser, event, elementType, elementID, createdAt").Fields("?,?,?,?,?,UTC_TIMESTAMP()").Prepare(),
get: acc.Select(as).Columns("actor, targetUser, event, elementType, elementID, createdAt").Where("asid = ?").Prepare(),
add: acc.Insert(as).Columns("actor,targetUser,event,elementType,elementID,createdAt,extra").Fields("?,?,?,?,?,UTC_TIMESTAMP(),?").Prepare(),
get: acc.Select(as).Columns("actor,targetUser,event,elementType,elementID,createdAt,extra").Where("asid=?").Prepare(),
delete: acc.Delete(as).Where("asid=?").Prepare(),
deleteByParams: acc.Delete(as).Where("event=? AND elementID=? AND elementType=?").Prepare(),
deleteByParamsExtra: acc.Delete(as).Where("event=? AND elementID=? AND elementType=? AND extra=?").Prepare(),
aidsByParamsExtra: acc.Select(as).Columns("asid").Where("event=? AND elementID=? AND elementType=? AND extra=?").Prepare(),
count: acc.Count(as).Prepare(),
}, acc.FirstError()
}
func (s *DefaultActivityStream) Add(a Alert) (int, error) {
res, err := s.add.Exec(a.ActorID, a.TargetUserID, a.Event, a.ElementType, a.ElementID)
res, err := s.add.Exec(a.ActorID, a.TargetUserID, a.Event, a.ElementType, a.ElementID, a.Extra)
if err != nil {
return 0, err
}
lastID, err := res.LastInsertId()
return int(lastID), err
}
func (s *DefaultActivityStream) Get(id int) (Alert, error) {
a := Alert{ASID: id}
err := s.get.QueryRow(id).Scan(&a.ActorID, &a.TargetUserID, &a.Event, &a.ElementType, &a.ElementID, &a.CreatedAt)
err := s.get.QueryRow(id).Scan(&a.ActorID, &a.TargetUserID, &a.Event, &a.ElementType, &a.ElementID, &a.CreatedAt, &a.Extra)
return a, err
}
func (s *DefaultActivityStream) Delete(id int) error {
_, err := s.delete.Exec(id)
return err
}
func (s *DefaultActivityStream) DeleteByParams(event string, elementID int, elementType string) error {
_, err := s.deleteByParams.Exec(event, elementID, elementType)
return err
}
func (s *DefaultActivityStream) DeleteByParamsExtra(event string, elementID int, elementType, extra string) error {
_, err := s.deleteByParamsExtra.Exec(event, elementID, elementType, extra)
return err
}
func (s *DefaultActivityStream) AidsByParamsExtra(event string, elementID int, elementType, extra string) (aids []int, err error) {
rows, err := s.aidsByParamsExtra.Query(event, elementID, elementType, extra)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var aid int
if err := rows.Scan(&aid); err != nil {
return nil, err
}
aids = append(aids, aid)
}
return aids, rows.Err()
}
// TODO: Write a test for this
// Count returns the total number of activity stream items
func (s *DefaultActivityStream) Count() (count int) {

View File

@ -12,10 +12,11 @@ import (
"strconv"
"strings"
"time"
//"fmt"
"github.com/Azareal/Gosora/common/phrases"
"github.com/Azareal/Gosora/query_gen"
qgen "github.com/Azareal/Gosora/query_gen"
)
type Alert struct {
@ -26,6 +27,7 @@ type Alert struct {
ElementType string
ElementID int
CreatedAt time.Time
Extra string
Actor *User
}
@ -45,10 +47,10 @@ func init() {
alertStmts = AlertStmts{
notifyWatchers: acc.SimpleInsertInnerJoin(
qgen.DBInsert{"activity_stream_matches", "watcher,asid", ""},
qgen.DBJoin{"activity_stream", "activity_subscriptions", "activity_subscriptions.user, activity_stream.asid", "activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor", "asid = ?", "", ""},
qgen.DBJoin{"activity_stream", "activity_subscriptions", "activity_subscriptions.user, activity_stream.asid", "activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor", "asid=?", "", ""},
),
notifyOne: acc.Insert("activity_stream_matches").Columns("watcher,asid").Fields("?,?").Prepare(),
getWatchers: acc.SimpleInnerJoin("activity_stream", "activity_subscriptions", "activity_subscriptions.user", "activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor", "asid = ?", "", ""),
getWatchers: acc.SimpleInnerJoin("activity_stream", "activity_subscriptions", "activity_subscriptions.user", "activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor", "asid=?", "", ""),
}
return acc.FirstError()
})
@ -123,6 +125,7 @@ func BuildAlert(alert Alert, user User /* The current user */) (out string, err
case "post":
topic, err := TopicByReplyID(alert.ElementID)
if err != nil {
DebugLogf("Unable to find linked topic by reply ID %d", alert.ElementID)
return "", errors.New(phrases.GetErrorPhrase("alerts_no_linked_topic_by_reply"))
}
url = topic.Link
@ -146,49 +149,49 @@ func BuildAlert(alert Alert, user User /* The current user */) (out string, err
return buildAlertString(phraseName, []string{alert.Actor.Name, area}, url, alert.Actor.Avatar, alert.ASID), nil
}
func buildAlertString(msg string, sub []string, path string, avatar string, asid int) string {
var substring string
func buildAlertString(msg string, sub []string, path, avatar string, asid int) string {
var subString string
for _, item := range sub {
substring += "\"" + escapeTextInJson(item) + "\","
subString += "\"" + escapeTextInJson(item) + "\","
}
if len(substring) > 0 {
substring = substring[:len(substring)-1]
if len(subString) > 0 {
subString = subString[:len(subString)-1]
}
return `{"msg":"` + escapeTextInJson(msg) + `","sub":[` + substring + `],"path":"` + escapeTextInJson(path) + `","avatar":"` + escapeTextInJson(avatar) + `","id":` + strconv.Itoa(asid) + `}`
return `{"msg":"` + escapeTextInJson(msg) + `","sub":[` + subString + `],"path":"` + escapeTextInJson(path) + `","avatar":"` + escapeTextInJson(avatar) + `","id":` + strconv.Itoa(asid) + `}`
}
func AddActivityAndNotifyAll(actor int, targetUser int, event string, elementType string, elementID int) error {
id, err := Activity.Add(Alert{ActorID: actor, TargetUserID: targetUser, Event: event, ElementType: elementType, ElementID: elementID})
func AddActivityAndNotifyAll(a Alert) error {
id, err := Activity.Add(a)
if err != nil {
return err
}
return NotifyWatchers(id)
}
func AddActivityAndNotifyTarget(alert Alert) error {
id, err := Activity.Add(alert)
func AddActivityAndNotifyTarget(a Alert) error {
id, err := Activity.Add(a)
if err != nil {
return err
}
err = NotifyOne(alert.TargetUserID, id)
err = NotifyOne(a.TargetUserID, id)
if err != nil {
return err
}
alert.ASID = id
a.ASID = id
// Live alerts, if the target is online and WebSockets is enabled
if EnableWebsockets {
go func() {
_ = WsHub.pushAlert(alert.TargetUserID, alert)
_ = WsHub.pushAlert(a.TargetUserID, a)
//fmt.Println("err:",err)
}()
}
return nil
}
func NotifyOne(watcher int, asid int) error {
func NotifyOne(watcher, asid int) error {
_, err := alertStmts.notifyOne.Exec(watcher, asid)
return err
}
@ -236,3 +239,7 @@ func notifyWatchers(asid int) {
}
_ = WsHub.pushAlerts(uids, alert)
}
func DismissAlert(uid, aid int) {
_ = WsHub.PushMessage(uid, `{"event":"dismiss-alert","id":`+strconv.Itoa(aid)+`}`)
}

View File

@ -3,8 +3,8 @@ package common
import (
"database/sql"
"errors"
"strings"
"os"
"strings"
qgen "github.com/Azareal/Gosora/query_gen"
)
@ -28,12 +28,12 @@ type AttachmentStore interface {
MiniGetList(originTable string, originID int) (alist []*MiniAttachment, err error)
BulkMiniGetList(originTable string, ids []int) (amap map[int][]*MiniAttachment, err error)
Add(sectionID int, sectionTable string, originID int, originTable string, uploadedBy int, path, extra string) (int, error)
MoveTo(sectionID int, originID int, originTable string) error
MoveTo(sectionID, originID int, originTable string) error
MoveToByExtra(sectionID int, originTable, extra string) error
Count() int
CountIn(originTable string, oid int) int
CountInPath(path string) int
Delete(aid int) error
Delete(id int) error
}
type DefaultAttachmentStore struct {
@ -55,10 +55,10 @@ func NewDefaultAttachmentStore(acc *qgen.Accumulator) (*DefaultAttachmentStore,
getByObj: acc.Select(a).Columns("attachID, sectionID, uploadedBy, path, extra").Where("originTable = ? AND originID = ?").Prepare(),
add: acc.Insert(a).Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path, extra").Fields("?,?,?,?,?,?,?").Prepare(),
count: acc.Count(a).Prepare(),
countIn: acc.Count(a).Where("originTable = ? and originID = ?").Prepare(),
countIn: acc.Count(a).Where("originTable=? and originID=?").Prepare(),
countInPath: acc.Count(a).Where("path = ?").Prepare(),
move: acc.Update(a).Set("sectionID = ?").Where("originID = ? AND originTable = ?").Prepare(),
moveByExtra: acc.Update(a).Set("sectionID = ?").Where("originTable = ? AND extra = ?").Prepare(),
move: acc.Update(a).Set("sectionID=?").Where("originID=? AND originTable=?").Prepare(),
moveByExtra: acc.Update(a).Set("sectionID=?").Where("originTable=? AND extra=?").Prepare(),
delete: acc.Delete(a).Where("attachID=?").Prepare(),
}, acc.FirstError()
}
@ -151,7 +151,7 @@ func (s *DefaultAttachmentStore) Add(sectionID int, sectionTable string, originI
return int(lid), err
}
func (s *DefaultAttachmentStore) MoveTo(sectionID int, originID int, originTable string) error {
func (s *DefaultAttachmentStore) MoveTo(sectionID, originID int, originTable string) error {
_, err := s.move.Exec(sectionID, originID, originTable)
return err
}
@ -185,8 +185,8 @@ func (s *DefaultAttachmentStore) CountInPath(path string) (count int) {
return count
}
func (s *DefaultAttachmentStore) Delete(aid int) error {
_, err := s.delete.Exec(aid)
func (s *DefaultAttachmentStore) Delete(id int) error {
_, err := s.delete.Exec(id)
return err
}

View File

@ -21,8 +21,8 @@ type LogItem struct {
}
type LogStore interface {
Create(action string, elementID int, elementType string, ip string, actorID int) (err error)
CreateExtra(action string, elementID int, elementType string, ip string, actorID int, extra string) (err error)
Create(action string, elementID int, elementType, ip string, actorID int) (err error)
CreateExtra(action string, elementID int, elementType, ip string, actorID int, extra string) (err error)
Count() int
GetOffset(offset, perPage int) (logs []LogItem, err error)
}
@ -43,11 +43,11 @@ func NewModLogStore(acc *qgen.Accumulator) (*SQLModLogStore, error) {
}
// TODO: Make a store for this?
func (s *SQLModLogStore) Create(action string, elementID int, elementType string, ip string, actorID int) (err error) {
func (s *SQLModLogStore) Create(action string, elementID int, elementType, ip string, actorID int) (err error) {
return s.CreateExtra(action, elementID, elementType, ip, actorID, "")
}
func (s *SQLModLogStore) CreateExtra(action string, elementID int, elementType string, ip string, actorID int, extra string) (err error) {
func (s *SQLModLogStore) CreateExtra(action string, elementID int, elementType, ip string, actorID int, extra string) (err error) {
_, err = s.create.Exec(action, elementID, elementType, ip, actorID, extra)
return err
}
@ -99,11 +99,11 @@ func NewAdminLogStore(acc *qgen.Accumulator) (*SQLAdminLogStore, error) {
}
// TODO: Make a store for this?
func (s *SQLAdminLogStore) Create(action string, elementID int, elementType string, ip string, actorID int) (err error) {
func (s *SQLAdminLogStore) Create(action string, elementID int, elementType, ip string, actorID int) (err error) {
return s.CreateExtra(action, elementID, elementType, ip, actorID, "")
}
func (s *SQLAdminLogStore) CreateExtra(action string, elementID int, elementType string, ip string, actorID int, extra string) (err error) {
func (s *SQLAdminLogStore) CreateExtra(action string, elementID int, elementType, ip string, actorID int, extra string) (err error) {
_, err = s.create.Exec(action, elementID, elementType, ip, actorID, extra)
return err
}

View File

@ -14,11 +14,14 @@ import (
"sync/atomic"
"time"
"github.com/Azareal/Gosora/query_gen"
qgen "github.com/Azareal/Gosora/query_gen"
meta "github.com/Azareal/Gosora/common/meta"
)
var SoftwareVersion = Version{Major: 0, Minor: 3, Patch: 0, Tag: "dev"}
var Meta meta.MetaStore
// nolint I don't want to write comments for each of these o.o
const Hour int = 60 * 60
const Day int = Hour * 24
@ -57,7 +60,7 @@ type StringList []string
// TODO: Let admins manage this from the Control Panel
// apng is commented out for now, as we have no way of re-encoding it into a smaller file
var AllowedFileExts = StringList{
"png", "jpg", "jpe","jpeg","jif","jfi","jfif", "svg", "bmp", "gif", "tiff","tif", "webp", /*"apng",*/ // images
"png", "jpg", "jpe", "jpeg", "jif", "jfi", "jfif", "svg", "bmp", "gif", "tiff", "tif", "webp", /*"apng",*/ // images
"txt", "xml", "json", "yaml", "toml", "ini", "md", "html", "rtf", "js", "py", "rb", "css", "scss", "less", "eqcss", "pcss", "java", "ts", "cs", "c", "cc", "cpp", "cxx", "C", "c++", "h", "hh", "hpp", "hxx", "h++", "rs", "rlib", "htaccess", "gitignore", /*"go","php",*/ // text
@ -68,7 +71,7 @@ var AllowedFileExts = StringList{
"bz2", "zip", "zipx", "gz", "7z", "tar", "cab", "rar", "kgb", "pea", "xz", "zz", // archives
}
var ImageFileExts = StringList{
"png", "jpg", "jpe","jpeg","jif","jfi","jfif", "svg", "bmp", "gif", "tiff","tif", "webp", /* "apng",*/
"png", "jpg", "jpe", "jpeg", "jif", "jfi", "jfif", "svg", "bmp", "gif", "tiff", "tif", "webp", /* "apng",*/
}
var ArchiveFileExts = StringList{
"bz2", "zip", "zipx", "gz", "7z", "tar", "cab", "rar", "kgb", "pea", "xz", "zz",
@ -152,3 +155,25 @@ func Log(args ...interface{}) {
func Logf(str string, args ...interface{}) {
log.Printf(str, args...)
}
func Countf(stmt *sql.Stmt, args ...interface{}) (count int) {
err := stmt.QueryRow(args...).Scan(&count)
if err != nil {
LogError(err)
}
return count
}
func eachall(stmt *sql.Stmt, f func(r *sql.Rows) error) error {
rows, err := stmt.Query()
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
if err := f(rows); err != nil {
return err
}
}
return rows.Err()
}

View File

@ -32,17 +32,18 @@ type ConvoStmts struct {
func init() {
DbInits.Add(func(acc *qgen.Accumulator) error {
cpo := "conversations_posts"
convoStmts = ConvoStmts{
fetchPost: acc.Select("conversations_posts").Columns("cid, body, post, createdBy").Where("pid = ?").Prepare(),
getPosts: acc.Select("conversations_posts").Columns("pid, body, post, createdBy").Where("cid = ?").Limit("?,?").Prepare(),
countPosts: acc.Count("conversations_posts").Where("cid = ?").Prepare(),
edit: acc.Update("conversations").Set("lastReplyBy = ?, lastReplyAt = ?").Where("cid = ?").Prepare(),
create: acc.Insert("conversations").Columns("createdAt, lastReplyAt").Fields("UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(),
has: acc.Count("conversations_participants").Where("uid = ? AND cid = ?").Prepare(),
fetchPost: acc.Select(cpo).Columns("cid,body,post,createdBy").Where("pid=?").Prepare(),
getPosts: acc.Select(cpo).Columns("pid,body,post,createdBy").Where("cid=?").Limit("?,?").Prepare(),
countPosts: acc.Count(cpo).Where("cid=?").Prepare(),
edit: acc.Update("conversations").Set("lastReplyBy=?,lastReplyAt=?").Where("cid=?").Prepare(),
create: acc.Insert("conversations").Columns("createdAt,lastReplyAt").Fields("UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(),
has: acc.Count("conversations_participants").Where("uid=? AND cid=?").Prepare(),
editPost: acc.Update("conversations_posts").Set("body = ?, post = ?").Where("pid = ?").Prepare(),
createPost: acc.Insert("conversations_posts").Columns("cid, body, post, createdBy").Fields("?,?,?,?").Prepare(),
deletePost: acc.Delete("conversations_posts").Where("pid = ?").Prepare(),
editPost: acc.Update(cpo).Set("body=?,post=?").Where("pid=?").Prepare(),
createPost: acc.Insert(cpo).Columns("cid,body,post,createdBy").Fields("?,?,?,?").Prepare(),
deletePost: acc.Delete(cpo).Where("pid=?").Prepare(),
getUsers: acc.Select("conversations_participants").Columns("uid").Where("cid = ?").Prepare(),
}
@ -138,7 +139,7 @@ type ConversationExtra struct {
type ConversationStore interface {
Get(id int) (*Conversation, error)
GetUser(uid int, offset int) (cos []*Conversation, err error)
GetUser(uid, offset int) (cos []*Conversation, err error)
GetUserExtra(uid int, offset int) (cos []*ConversationExtra, err error)
GetUserCount(uid int) (count int)
Delete(id int) error
@ -160,12 +161,12 @@ type DefaultConversationStore struct {
func NewDefaultConversationStore(acc *qgen.Accumulator) (*DefaultConversationStore, error) {
return &DefaultConversationStore{
get: acc.Select("conversations").Columns("createdBy, createdAt, lastReplyBy, lastReplyAt").Where("cid = ?").Prepare(),
getUser: acc.SimpleInnerJoin("conversations_participants AS cp", "conversations AS c", "cp.cid, c.createdBy, c.createdAt, c.lastReplyBy, c.lastReplyAt", "cp.cid = c.cid", "cp.uid = ?", "c.lastReplyAt DESC, c.createdAt DESC, c.cid DESC", "?,?"),
getUserCount: acc.Count("conversations_participants").Where("uid = ?").Prepare(),
delete: acc.Delete("conversations").Where("cid = ?").Prepare(),
deletePosts: acc.Delete("conversations_posts").Where("cid = ?").Prepare(),
deleteParticipants: acc.Delete("conversations_participants").Where("cid = ?").Prepare(),
get: acc.Select("conversations").Columns("createdBy, createdAt, lastReplyBy, lastReplyAt").Where("cid=?").Prepare(),
getUser: acc.SimpleInnerJoin("conversations_participants AS cp", "conversations AS c", "cp.cid, c.createdBy, c.createdAt, c.lastReplyBy, c.lastReplyAt", "cp.cid=c.cid", "cp.uid=?", "c.lastReplyAt DESC, c.createdAt DESC, c.cid DESC", "?,?"),
getUserCount: acc.Count("conversations_participants").Where("uid=?").Prepare(),
delete: acc.Delete("conversations").Where("cid=?").Prepare(),
deletePosts: acc.Delete("conversations_posts").Where("cid=?").Prepare(),
deleteParticipants: acc.Delete("conversations_participants").Where("cid=?").Prepare(),
create: acc.Insert("conversations").Columns("createdBy, createdAt, lastReplyAt").Fields("?,UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(),
addParticipant: acc.Insert("conversations_participants").Columns("uid, cid").Fields("?,?").Prepare(),
count: acc.Count("conversations").Prepare(),
@ -178,7 +179,7 @@ func (s *DefaultConversationStore) Get(id int) (*Conversation, error) {
return co, err
}
func (s *DefaultConversationStore) GetUser(uid int, offset int) (cos []*Conversation, err error) {
func (s *DefaultConversationStore) GetUser(uid, offset int) (cos []*Conversation, err error) {
rows, err := s.getUser.Query(uid, offset, Config.ItemsPerPage)
if err != nil {
return nil, err
@ -197,7 +198,7 @@ func (s *DefaultConversationStore) GetUser(uid int, offset int) (cos []*Conversa
return cos, rows.Err()
}
func (s *DefaultConversationStore) GetUserExtra(uid int, offset int) (cos []*ConversationExtra, err error) {
func (s *DefaultConversationStore) GetUserExtra(uid, offset int) (cos []*ConversationExtra, err error) {
raw, err := s.GetUser(uid, offset)
if err != nil {
return nil, err

View File

@ -10,7 +10,7 @@ import (
p "github.com/Azareal/Gosora/common/phrases"
)
func SendActivationEmail(username string, email string, token string) error {
func SendActivationEmail(username, email, token string) error {
schema := "http"
if Config.SslSchema {
schema += "s"
@ -21,30 +21,30 @@ func SendActivationEmail(username string, email string, token string) error {
return SendEmail(email, subject, msg)
}
func SendValidationEmail(username string, email string, token string) error {
func SendValidationEmail(username, email, token string) error {
schema := "http"
if Config.SslSchema {
schema += "s"
}
r := func(body *string) func(name, val string) {
return func(name, val string) {
*body = strings.Replace(*body,"{{"+name+"}}",val,-1)
*body = strings.Replace(*body, "{{"+name+"}}", val, -1)
}
}
subject := p.GetAccountPhrase("ValidateEmailSubject")
r1 := r(&subject)
r1("name",Site.Name)
r1("name", Site.Name)
body := p.GetAccountPhrase("ValidateEmailBody")
r2 := r(&body)
r2("username",username)
r2("schema",schema)
r2("url",Site.URL)
r2("token",token)
r2("username", username)
r2("schema", schema)
r2("url", Site.URL)
r2("token", token)
return SendEmail(email, subject, body)
}
// TODO: Refactor this
func SendEmail(email string, subject string, msg string) (err error) {
func SendEmail(email, subject, msg string) (err error) {
// This hook is useful for plugin_sendmail or for testing tools. Possibly to hook it into some sort of mail server?
ret, hasHook := GetHookTable().VhookNeedHook("email_send_intercept", email, subject, msg)
if hasHook {

View File

@ -18,8 +18,8 @@ type Email struct {
type EmailStore interface {
// TODO: Add an autoincrement key
Get(user *User, email string) (Email, error)
GetEmailsByUser(user *User) (emails []Email, err error)
Get(u *User, email string) (Email, error)
GetEmailsByUser(u *User) (emails []Email, err error)
Add(uid int, email, token string) error
Delete(uid int, email string) error
VerifyEmail(email string) error
@ -47,7 +47,7 @@ func NewDefaultEmailStore(acc *qgen.Accumulator) (*DefaultEmailStore, error) {
}
func (s *DefaultEmailStore) Get(user *User, email string) (Email, error) {
e := Email{UserID:user.ID, Primary:email !="" && user.Email==email}
e := Email{UserID: user.ID, Primary: email != "" && user.Email == email}
err := s.get.QueryRow(user.ID, email).Scan(&e.Email, &e.Validated, &e.Token)
return e, err
}
@ -72,13 +72,13 @@ func (s *DefaultEmailStore) GetEmailsByUser(user *User) (emails []Email, err err
return emails, rows.Err()
}
func (s *DefaultEmailStore) Add(uid int, email string, token string) error {
func (s *DefaultEmailStore) Add(uid int, email, token string) error {
_, err := s.add.Exec(uid, email, 0, token)
return err
}
func (s *DefaultEmailStore) Delete(uid int, email string) error {
_, err := s.delete.Exec(uid,email)
_, err := s.delete.Exec(uid, email)
return err
}

View File

@ -59,7 +59,7 @@ func (list SFileList) JSTmplInit() error {
tmplName := strings.TrimSuffix(path, ".jgo")
shortName := strings.TrimPrefix(tmplName, "template_")
replace := func(data []byte, replaceThis string, withThis string) []byte {
replace := func(data []byte, replaceThis, withThis string) []byte {
return bytes.Replace(data, []byte(replaceThis), []byte(withThis), -1)
}
@ -107,7 +107,7 @@ func (list SFileList) JSTmplInit() error {
}*/
// ? Can we just use a regex? I'm thinking of going more efficient, or just outright rolling wasm, this is a temp hack in a place where performance doesn't particularly matter
each := func(phrase string, handle func(index int)) {
each := func(phrase string, h func(index int)) {
//fmt.Println("find each '" + phrase + "'")
index := endBrace
if index < 0 {
@ -121,7 +121,7 @@ func (list SFileList) JSTmplInit() error {
if !foundIt {
break
}
handle(index)
h(index)
}
}
each("strconv.Itoa(", func(index int) {
@ -292,7 +292,7 @@ func (list SFileList) Init() error {
})
}
func (list SFileList) Add(path string, prefix string) error {
func (list SFileList) Add(path, prefix string) error {
data, err := ioutil.ReadFile(path)
if err != nil {
return err

View File

@ -7,7 +7,7 @@ import (
"strconv"
"strings"
"github.com/Azareal/Gosora/query_gen"
qgen "github.com/Azareal/Gosora/query_gen"
_ "github.com/go-sql-driver/mysql"
)
@ -60,8 +60,8 @@ var forumStmts ForumStmts
func init() {
DbInits.Add(func(acc *qgen.Accumulator) error {
forumStmts = ForumStmts{
update: acc.Update("forums").Set("name = ?, desc = ?, active = ?, preset = ?").Where("fid = ?").Prepare(),
setPreset: acc.Update("forums").Set("preset = ?").Where("fid = ?").Prepare(),
update: acc.Update("forums").Set("name=?,desc=?,active=?,preset=?").Where("fid=?").Prepare(),
setPreset: acc.Update("forums").Set("preset=?").Where("fid=?").Prepare(),
}
return acc.FirstError()
})
@ -74,7 +74,7 @@ func (f *Forum) Copy() (fcopy Forum) {
}
// TODO: Write tests for this
func (f *Forum) Update(name string, desc string, active bool, preset string) error {
func (f *Forum) Update(name, desc string, active bool, preset string) error {
if name == "" {
name = f.Name
}
@ -137,6 +137,7 @@ func (sf SortForum) Len() int {
func (sf SortForum) Swap(i, j int) {
sf[i], sf[j] = sf[j], sf[i]
}
/*func (sf SortForum) Less(i,j int) bool {
l := sf.less(i,j)
if l {
@ -156,7 +157,7 @@ func (sf SortForum) Less(i, j int) bool {
}
// ! Don't use this outside of tests and possibly template_init.go
func BlankForum(fid int, link string, name string, desc string, active bool, preset string, parentID int, parentType string, topicCount int) *Forum {
func BlankForum(fid int, link, name, desc string, active bool, preset string, parentID int, parentType string, topicCount int) *Forum {
return &Forum{ID: fid, Link: link, Name: name, Desc: desc, Active: active, Preset: preset, ParentID: parentID, ParentType: parentType, TopicCount: topicCount}
}

View File

@ -25,9 +25,9 @@ func NewDefaultIPSearcher() (*DefaultIPSearcher, error) {
uu := "users"
return &DefaultIPSearcher{
searchUsers: acc.Select(uu).Columns("uid").Where("last_ip=? OR last_ip LIKE CONCAT('%-',?)").Prepare(),
searchTopics: acc.Select(uu).Columns("uid").InQ("uid", acc.Select("topics").Columns("createdBy").Where("ipaddress=?")).Prepare(),
searchReplies: acc.Select(uu).Columns("uid").InQ("uid", acc.Select("replies").Columns("createdBy").Where("ipaddress=?")).Prepare(),
searchUsersReplies: acc.Select(uu).Columns("uid").InQ("uid", acc.Select("users_replies").Columns("createdBy").Where("ipaddress=?")).Prepare(),
searchTopics: acc.Select(uu).Columns("uid").InQ("uid", acc.Select("topics").Columns("createdBy").Where("ip=?")).Prepare(),
searchReplies: acc.Select(uu).Columns("uid").InQ("uid", acc.Select("replies").Columns("createdBy").Where("ip=?")).Prepare(),
searchUsersReplies: acc.Select(uu).Columns("uid").InQ("uid", acc.Select("users_replies").Columns("createdBy").Where("ip=?")).Prepare(),
}, acc.FirstError()
}

View File

@ -10,22 +10,25 @@ var Likes LikeStore
type LikeStore interface {
BulkExists(ids []int, sentBy int, targetType string) ([]int, error)
Delete(targetID int, targetType string) error
Count() (count int)
}
type DefaultLikeStore struct {
count *sql.Stmt
delete *sql.Stmt
}
func NewDefaultLikeStore(acc *qgen.Accumulator) (*DefaultLikeStore, error) {
return &DefaultLikeStore{
count: acc.Count("likes").Prepare(),
delete: acc.Delete("likes").Where("targetItem=? AND targetType=?").Prepare(),
}, acc.FirstError()
}
// TODO: Write a test for this
func (s *DefaultLikeStore) BulkExists(ids []int, sentBy int, targetType string) (eids []int, err error) {
rows, err := qgen.NewAcc().Select("likes").Columns("targetItem").Where("sentBy = ? AND targetType = ?").In("targetItem", ids).Query(sentBy, targetType)
rows, err := qgen.NewAcc().Select("likes").Columns("targetItem").Where("sentBy=? AND targetType=?").In("targetItem", ids).Query(sentBy, targetType)
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
@ -43,6 +46,11 @@ func (s *DefaultLikeStore) BulkExists(ids []int, sentBy int, targetType string)
return eids, rows.Err()
}
func (s *DefaultLikeStore) Delete(targetID int, targetType string) error {
_, err := s.delete.Exec(targetID, targetType)
return err
}
// TODO: Write a test for this
// Count returns the total number of likes globally
func (s *DefaultLikeStore) Count() (count int) {

View File

@ -5,7 +5,7 @@ import (
"strconv"
"sync/atomic"
"github.com/Azareal/Gosora/query_gen"
qgen "github.com/Azareal/Gosora/query_gen"
)
var Menus *DefaultMenuStore
@ -40,7 +40,7 @@ func (s *DefaultMenuStore) Get(mid int) (*MenuListHolder, error) {
}
func (s *DefaultMenuStore) Items(mid int) (mlist MenuItemList, err error) {
err = qgen.NewAcc().Select("menu_items").Columns("miid,name,htmlID,cssClass,position,path,aria,tooltip,order,tmplName,guestOnly,memberOnly,staffOnly,adminOnly").Where("mid = " + strconv.Itoa(mid)).Orderby("order ASC").Each(func(rows *sql.Rows) error {
err = qgen.NewAcc().Select("menu_items").Columns("miid,name,htmlID,cssClass,position,path,aria,tooltip,order,tmplName,guestOnly,memberOnly,staffOnly,adminOnly").Where("mid=" + strconv.Itoa(mid)).Orderby("order ASC").Each(func(rows *sql.Rows) error {
i := MenuItem{MenuID: mid}
err := rows.Scan(&i.ID, &i.Name, &i.HTMLID, &i.CSSClass, &i.Position, &i.Path, &i.Aria, &i.Tooltip, &i.Order, &i.TmplName, &i.GuestOnly, &i.MemberOnly, &i.SuperModOnly, &i.AdminOnly)
if err != nil {

View File

@ -1,14 +1,15 @@
package common
import "database/sql"
import "github.com/Azareal/Gosora/query_gen"
import (
"database/sql"
var Meta MetaStore
qgen "github.com/Azareal/Gosora/query_gen"
)
// MetaStore is a simple key-value store for the system to stash things in when needed
type MetaStore interface {
Get(name string) (val string, err error)
Set(name string, val string) error
Set(name, val string) error
}
type DefaultMetaStore struct {
@ -32,7 +33,7 @@ func (s *DefaultMetaStore) Get(name string) (val string, err error) {
}
// TODO: Use timestamped rows as a more robust method of ensuring data integrity
func (s *DefaultMetaStore) Set(name string, val string) error {
func (s *DefaultMetaStore) Set(name, val string) error {
_, err := s.Get(name)
if err == sql.ErrNoRows {
_, err := s.add.Exec(name)

View File

@ -31,7 +31,7 @@ func init() {
DbInits.Add(func(acc *qgen.Accumulator) error {
rl := "registration_logs"
regLogStmts = RegLogStmts{
update: acc.Update(rl).Set("username = ?, email = ?, failureReason = ?, success = ?").Where("rlid = ?").Prepare(),
update: acc.Update(rl).Set("username=?, email=?, failureReason=?, success=?").Where("rlid=?").Prepare(),
create: acc.Insert(rl).Columns("username, email, failureReason, success, ipaddress, doneAt").Fields("?,?,?,?,?,UTC_TIMESTAMP()").Prepare(),
}
return acc.FirstError()
@ -57,7 +57,7 @@ func (l *RegLogItem) Create() (id int, err error) {
type RegLogStore interface {
Count() (count int)
GetOffset(offset int, perPage int) (logs []RegLogItem, err error)
GetOffset(offset, perPage int) (logs []RegLogItem, err error)
}
type SQLRegLogStore struct {
@ -120,8 +120,8 @@ func init() {
DbInits.Add(func(acc *qgen.Accumulator) error {
ll := "login_logs"
loginLogStmts = LoginLogStmts{
update: acc.Update(ll).Set("uid = ?, success = ?").Where("lid = ?").Prepare(),
create: acc.Insert(ll).Columns("uid, success, ipaddress, doneAt").Fields("?,?,?,UTC_TIMESTAMP()").Prepare(),
update: acc.Update(ll).Set("uid=?,success=?").Where("lid=?").Prepare(),
create: acc.Insert(ll).Columns("uid,success,ipaddress,doneAt").Fields("?,?,?,UTC_TIMESTAMP()").Prepare(),
}
return acc.FirstError()
})
@ -147,7 +147,7 @@ func (l *LoginLogItem) Create() (id int, err error) {
type LoginLogStore interface {
Count() (count int)
CountUser(uid int) (count int)
GetOffset(uid int, offset int, perPage int) (logs []LoginLogItem, err error)
GetOffset(uid, offset, perPage int) (logs []LoginLogItem, err error)
}
type SQLLoginLogStore struct {
@ -160,8 +160,8 @@ func NewLoginLogStore(acc *qgen.Accumulator) (*SQLLoginLogStore, error) {
ll := "login_logs"
return &SQLLoginLogStore{
count: acc.Count(ll).Prepare(),
countForUser: acc.Count(ll).Where("uid = ?").Prepare(),
getOffsetByUser: acc.Select(ll).Columns("lid, success, ipaddress, doneAt").Where("uid = ?").Orderby("doneAt DESC").Limit("?,?").Prepare(),
countForUser: acc.Count(ll).Where("uid=?").Prepare(),
getOffsetByUser: acc.Select(ll).Columns("lid,success,ipaddress,doneAt").Where("uid=?").Orderby("doneAt DESC").Limit("?,?").Prepare(),
}, acc.FirstError()
}
@ -181,7 +181,7 @@ func (s *SQLLoginLogStore) CountUser(uid int) (count int) {
return count
}
func (s *SQLLoginLogStore) GetOffset(uid int, offset int, perPage int) (logs []LoginLogItem, err error) {
func (s *SQLLoginLogStore) GetOffset(uid, offset, perPage int) (logs []LoginLogItem, err error) {
rows, err := s.getOffsetByUser.Query(uid, offset, perPage)
if err != nil {
return logs, err

View File

@ -18,8 +18,8 @@ var customPageStmts CustomPageStmts
func init() {
DbInits.Add(func(acc *qgen.Accumulator) error {
customPageStmts = CustomPageStmts{
update: acc.Update("pages").Set("name = ?, title = ?, body = ?, allowedGroups = ?, menuID = ?").Where("pid = ?").Prepare(),
create: acc.Insert("pages").Columns("name, title, body, allowedGroups, menuID").Fields("?,?,?,?,?").Prepare(),
update: acc.Update("pages").Set("name=?,title=?,body=?,allowedGroups=?,menuID=?").Where("pid=?").Prepare(),
create: acc.Insert("pages").Columns("name,title,body,allowedGroups,menuID").Fields("?,?,?,?,?").Prepare(),
}
return acc.FirstError()
})
@ -74,7 +74,7 @@ type PageStore interface {
Count() (count int)
Get(id int) (*CustomPage, error)
GetByName(name string) (*CustomPage, error)
GetOffset(offset int, perPage int) (pages []*CustomPage, err error)
GetOffset(offset, perPage int) (pages []*CustomPage, err error)
Reload(id int) error
Delete(id int) error
}
@ -91,11 +91,11 @@ type DefaultPageStore struct {
func NewDefaultPageStore(acc *qgen.Accumulator) (*DefaultPageStore, error) {
pa := "pages"
return &DefaultPageStore{
get: acc.Select(pa).Columns("name, title, body, allowedGroups, menuID").Where("pid = ?").Prepare(),
getByName: acc.Select(pa).Columns("pid, name, title, body, allowedGroups, menuID").Where("name = ?").Prepare(),
get: acc.Select(pa).Columns("name, title, body, allowedGroups, menuID").Where("pid=?").Prepare(),
getByName: acc.Select(pa).Columns("pid, name, title, body, allowedGroups, menuID").Where("name=?").Prepare(),
getOffset: acc.Select(pa).Columns("pid, name, title, body, allowedGroups, menuID").Orderby("pid DESC").Limit("?,?").Prepare(),
count: acc.Count(pa).Prepare(),
delete: acc.Delete(pa).Where("pid = ?").Prepare(),
delete: acc.Delete(pa).Where("pid=?").Prepare(),
}, acc.FirstError()
}

View File

@ -108,7 +108,7 @@ type TagToAction struct {
}
// TODO: Write a test for this
func tryStepForward(i int, step int, runes []rune) (int, bool) {
func tryStepForward(i, step int, runes []rune) (int, bool) {
i += step
if i < len(runes) {
return i, true
@ -117,7 +117,7 @@ func tryStepForward(i int, step int, runes []rune) (int, bool) {
}
// TODO: Write a test for this
func tryStepBackward(i int, step int, runes []rune) (int, bool) {
func tryStepBackward(i, step int, runes []rune) (int, bool) {
if i == 0 {
return i, false
}
@ -369,7 +369,7 @@ func PreparseMessage(msg string) string {
// TODO: Test this
// TODO: Use this elsewhere in the parser?
func peek(cur int, skip int, runes []rune) rune {
func peek(cur, skip int, runes []rune) rune {
if (cur + skip) < len(runes) {
return runes[cur+skip]
}
@ -972,7 +972,7 @@ func parseMediaString(data string, settings *ParseSettings) (media MediaEmbed, o
}
// TODO: Write a test for this
func CoerceIntString(data string) (res int, length int) {
func CoerceIntString(data string) (res, length int) {
if !(data[0] > 47 && data[0] < 58) {
return 0, 1
}

View File

@ -174,7 +174,7 @@ func PresetToLang(preset string) string {
// TODO: Is this racey?
// TODO: Test this along with the rest of the perms system
func RebuildGroupPermissions(group *Group) error {
func RebuildGroupPermissions(g *Group) error {
var permstr []byte
log.Print("Reloading a group")
@ -185,7 +185,7 @@ func RebuildGroupPermissions(group *Group) error {
}
defer getGroupPerms.Close()
err = getGroupPerms.QueryRow(group.ID).Scan(&permstr)
err = getGroupPerms.QueryRow(g.ID).Scan(&permstr)
if err != nil {
return err
}
@ -197,15 +197,15 @@ func RebuildGroupPermissions(group *Group) error {
if err != nil {
return err
}
group.Perms = tmpPerms
g.Perms = tmpPerms
return nil
}
func OverridePerms(perms *Perms, status bool) {
func OverridePerms(p *Perms, status bool) {
if status {
*perms = AllPerms
*p = AllPerms
} else {
*perms = BlankPerms
*p = BlankPerms
}
}

View File

@ -66,18 +66,17 @@ func InitPluginLangs() error {
continue
}
e := func(field string, name string) error {
return errors.New("The "+field+" field must not be blank on plugin '" + name + "'")
e := func(field, name string) error {
return errors.New("The " + field + " field must not be blank on plugin '" + name + "'")
}
if plugin.UName == "" {
return e("UName",pluginItem)
return e("UName", pluginItem)
}
if plugin.Name == "" {
return e("Name",pluginItem)
return e("Name", pluginItem)
}
if plugin.Author == "" {
return e("Author",pluginItem)
return e("Author", pluginItem)
}
if plugin.Main == "" {
return errors.New("Couldn't find a main file for plugin '" + pluginItem + "'")

View File

@ -22,7 +22,7 @@ type Poll struct {
VoteCount int
}
func (p *Poll) CastVote(optionIndex int, uid int, ip string) error {
func (p *Poll) CastVote(optionIndex, uid int, ip string) error {
return Polls.CastVote(optionIndex, p.ID, uid, ip) // TODO: Move the query into a pollStmts rather than having it in the store
}

View File

@ -61,9 +61,9 @@ func NewDefaultPollStore(cache PollCache) (*DefaultPollStore, error) {
exists: acc.Select("polls").Columns("pollID").Where("pollID = ?").Prepare(),
createPoll: acc.Insert("polls").Columns("parentID, parentTable, type, options").Fields("?,?,?,?").Prepare(),
createPollOption: acc.Insert("polls_options").Columns("pollID, option, votes").Fields("?,?,0").Prepare(),
addVote: acc.Insert("polls_votes").Columns("pollID, uid, option, castAt, ipaddress").Fields("?,?,?,UTC_TIMESTAMP(),?").Prepare(),
addVote: acc.Insert("polls_votes").Columns("pollID,uid,option,castAt,ip").Fields("?,?,?,UTC_TIMESTAMP(),?").Prepare(),
incVoteCount: acc.Update("polls").Set("votes = votes + 1").Where("pollID = ?").Prepare(),
incVoteCountForOption: acc.Update("polls_options").Set("votes = votes + 1").Where("option = ? AND pollID = ?").Prepare(),
incVoteCountForOption: acc.Update("polls_options").Set("votes = votes + 1").Where("option=? AND pollID=?").Prepare(),
//count: acc.SimpleCount("polls", "", ""),
}, acc.FirstError()
}

View File

@ -3,6 +3,7 @@ package common
import (
"database/sql"
"html"
"strconv"
"time"
qgen "github.com/Azareal/Gosora/query_gen"
@ -45,9 +46,20 @@ func BlankProfileReply(id int) *ProfileReply {
}
// TODO: Write tests for this
// TODO: Remove alerts.
func (r *ProfileReply) Delete() error {
_, err := profileReplyStmts.delete.Exec(r.ID)
if err != nil {
return err
}
// TODO: Better coupling between the two paramsextra queries
aids, err := Activity.AidsByParamsExtra("reply", r.ParentID, "user", strconv.Itoa(r.ID))
if err != nil {
return err
}
for _, aid := range aids {
DismissAlert(r.ParentID, aid)
}
err = Activity.DeleteByParamsExtra("reply", r.ParentID, "user", strconv.Itoa(r.ID))
return err
}

View File

@ -10,6 +10,7 @@ var Prstore ProfileReplyStore
type ProfileReplyStore interface {
Get(id int) (*ProfileReply, error)
Exists(id int) bool
Create(profileID int, content string, createdBy int, ip string) (id int, err error)
Count() (count int)
}
@ -18,6 +19,7 @@ type ProfileReplyStore interface {
// TODO: Add more methods to this like Create()
type SQLProfileReplyStore struct {
get *sql.Stmt
exists *sql.Stmt
create *sql.Stmt
count *sql.Stmt
}
@ -25,8 +27,9 @@ type SQLProfileReplyStore struct {
func NewSQLProfileReplyStore(acc *qgen.Accumulator) (*SQLProfileReplyStore, error) {
ur := "users_replies"
return &SQLProfileReplyStore{
get: acc.Select(ur).Columns("uid, content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress").Where("rid = ?").Prepare(),
create: acc.Insert(ur).Columns("uid, content, parsed_content, createdAt, createdBy, ipaddress").Fields("?,?,?,UTC_TIMESTAMP(),?,?").Prepare(),
get: acc.Select(ur).Columns("uid, content, createdBy, createdAt, lastEdit, lastEditBy, ip").Where("rid=?").Prepare(),
exists: acc.Exists(ur, "rid").Prepare(),
create: acc.Insert(ur).Columns("uid, content, parsed_content, createdAt, createdBy, ip").Fields("?,?,?,UTC_TIMESTAMP(),?,?").Prepare(),
count: acc.Count(ur).Prepare(),
}, acc.FirstError()
}
@ -37,6 +40,14 @@ func (s *SQLProfileReplyStore) Get(id int) (*ProfileReply, error) {
return &r, err
}
func (s *SQLProfileReplyStore) Exists(id int) bool {
err := s.exists.QueryRow(id).Scan(&id)
if err != nil && err != ErrNoRows {
LogError(err)
}
return err != ErrNoRows
}
func (s *SQLProfileReplyStore) Create(profileID int, content string, createdBy int, ip string) (id int, err error) {
if Config.DisablePostIP {
ip = "0"

View File

@ -12,7 +12,7 @@ var ErrExceededRateLimit = errors.New("You're exceeding a rate limit. Please wai
// TODO: Persist rate limits to disk
type RateLimiter interface {
LimitIP(limit string, ip string) error
LimitIP(limit, ip string) error
LimitUser(limit string, user int) error
}
@ -83,7 +83,7 @@ func NewDefaultRateLimiter() *DefaultRateLimiter {
}}
}
func (l *DefaultRateLimiter) LimitIP(limit string, ip string) error {
func (l *DefaultRateLimiter) LimitIP(limit, ip string) error {
limiter, ok := l.limits[limit]
if !ok {
return ErrBadRateLimiter

177
common/recalc.go Normal file
View File

@ -0,0 +1,177 @@
package common
import (
"database/sql"
//"log"
"strconv"
qgen "github.com/Azareal/Gosora/query_gen"
)
var Recalc RecalcInt
type RecalcInt interface {
Replies() (count int, err error)
Subscriptions() (count int, err error)
ActivityStream() (count int, err error)
Users() error
Attachments() (count int, err error)
}
type DefaultRecalc struct {
getActivitySubscriptions *sql.Stmt
getActivityStream *sql.Stmt
getAttachments *sql.Stmt
}
func NewDefaultRecalc(acc *qgen.Accumulator) (*DefaultRecalc, error) {
return &DefaultRecalc{
getActivitySubscriptions: acc.Select("activity_subscriptions").Columns("targetID,targetType").Prepare(),
getActivityStream: acc.Select("activity_stream").Columns("asid,event,elementID,elementType,extra").Prepare(),
getAttachments: acc.Select("attachments").Columns("attachID,originID,originTable").Prepare(),
}, acc.FirstError()
}
func (s *DefaultRecalc) Replies() (count int, err error) {
var ltid int
err = Rstore.Each(func(r *Reply) error {
if ltid == r.ParentID && r.ParentID > 0 {
//return nil
}
if !Topics.Exists(r.ParentID) {
// TODO: Delete in chunks not one at a time?
if err := r.Delete(); err != nil {
return err
}
count++
}
return nil
})
return count, err
}
func (s *DefaultRecalc) Subscriptions() (count int, err error) {
err = eachall(s.getActivitySubscriptions, func(r *sql.Rows) error {
var targetID int
var targetType string
err := r.Scan(&targetID, &targetType)
if err != nil {
return err
}
if targetType == "topic" {
if !Topics.Exists(targetID) {
// TODO: Delete in chunks not one at a time?
err := Subscriptions.DeleteResource(targetID, targetType)
if err != nil {
return err
}
count++
}
}
return nil
})
return count, err
}
type Existable interface {
Exists(id int) bool
}
func (s *DefaultRecalc) ActivityStream() (count int, err error) {
err = eachall(s.getActivityStream, func(r *sql.Rows) error {
var asid, elementID int
var event, elementType, extra string
err := r.Scan(&asid, &event, &elementID, &elementType, &extra)
if err != nil {
return err
}
//log.Print("asid:",asid)
var s Existable
switch elementType {
case "user":
if event == "reply" {
extraI, _ := strconv.Atoi(extra)
if extraI > 0 {
s = Prstore
elementID = extraI
} else {
return nil
}
} else {
return nil
}
case "topic":
s = Topics
// TODO: Delete reply events with an empty extra field
if event == "reply" {
extraI, _ := strconv.Atoi(extra)
if extraI > 0 {
s = Rstore
elementID = extraI
}
}
case "post":
s = Rstore
// TODO: Add a TopicExistsByReplyID for efficiency
/*_, err = TopicByReplyID(elementID)
if err == sql.ErrNoRows {
// TODO: Delete in chunks not one at a time?
err := Activity.Delete(asid)
if err != nil {
return err
}
count++
} else if err != nil {
return err
}*/
default:
return nil
}
if !s.Exists(elementID) {
// TODO: Delete in chunks not one at a time?
err := Activity.Delete(asid)
if err != nil {
return err
}
count++
}
return nil
})
return count, err
}
func (s *DefaultRecalc) Users() error {
return Users.Each(func(u *User) error {
return u.RecalcPostStats()
})
}
func (s *DefaultRecalc) Attachments() (count int, err error) {
err = eachall(s.getAttachments, func(r *sql.Rows) error {
var aid, originID int
var originType string
err := r.Scan(&aid, &originID, &originType)
if err != nil {
return err
}
var s Existable
switch originType {
case "topics":
s = Topics
case "replies":
s = Rstore
default:
return nil
}
if !s.Exists(originID) {
// TODO: Delete in chunks not one at a time?
err := Attachments.Delete(aid)
if err != nil {
return err
}
count++
}
return nil
})
return count, err
}

View File

@ -11,6 +11,7 @@ import (
"errors"
"html"
"time"
"strconv"
qgen "github.com/Azareal/Gosora/query_gen"
)
@ -69,6 +70,8 @@ type ReplyStmts struct {
updateTopicReplies *sql.Stmt
updateTopicReplies2 *sql.Stmt
getAidsOfReply *sql.Stmt
}
func init() {
@ -89,6 +92,8 @@ func init() {
// TODO: Optimise this to avoid firing an update if it's not the last reply in a topic. We will need to set lastReplyID properly in other places and in the patcher first so we can use it here.
updateTopicReplies: acc.RawPrepare("UPDATE topics t INNER JOIN replies r ON t.tid = r.tid SET t.lastReplyBy = r.createdBy, t.lastReplyAt = r.createdAt, t.lastReplyID = r.rid WHERE t.tid = ?"),
updateTopicReplies2: acc.Update("topics").Set("lastReplyAt=createdAt,lastReplyBy=createdBy,lastReplyID=0").Where("postCount=1 AND tid=?").Prepare(),
getAidsOfReply: acc.Select("attachments").Columns("attachID").Where("originID=? AND originTable='replies'").Prepare(),
}
return acc.FirstError()
})
@ -120,7 +125,6 @@ func (r *Reply) Like(uid int) (err error) {
}
// TODO: Refresh topic list?
// TODO: Restructure alerts so we can delete the "x replied to topic" ones too.
func (r *Reply) Delete() error {
creator, err := Users.Get(r.CreatedBy)
if err == nil {
@ -158,6 +162,14 @@ func (r *Reply) Delete() error {
if err != nil {
return err
}
err = handleReplyAttachments(r.ID)
if err != nil {
return err
}
err = Activity.DeleteByParamsExtra("reply",r.ParentID,"topic",strconv.Itoa(r.ID))
if err != nil {
return err
}
_, err = replyStmts.deleteActivitySubs.Exec(r.ID)
if err != nil {
return err

View File

@ -11,8 +11,13 @@ var Rstore ReplyStore
type ReplyStore interface {
Get(id int) (*Reply, error)
Create(topic *Topic, content string, ip string, uid int) (id int, err error)
Each(f func(*Reply) error) error
Exists(id int) bool
Create(t *Topic, content string, ip string, uid int) (id int, err error)
Count() (count int)
CountUser(uid int) (count int)
CountMegaUser(uid int) (count int)
CountBigUser(uid int) (count int)
SetCache(cache ReplyCache)
GetCache() ReplyCache
@ -22,8 +27,12 @@ type SQLReplyStore struct {
cache ReplyCache
get *sql.Stmt
getAll *sql.Stmt
exists *sql.Stmt
create *sql.Stmt
count *sql.Stmt
countUser *sql.Stmt
countWordUser *sql.Stmt
}
func NewSQLReplyStore(acc *qgen.Accumulator, cache ReplyCache) (*SQLReplyStore, error) {
@ -33,9 +42,13 @@ func NewSQLReplyStore(acc *qgen.Accumulator, cache ReplyCache) (*SQLReplyStore,
re := "replies"
return &SQLReplyStore{
cache: cache,
get: acc.Select(re).Columns("tid, content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress, likeCount, attachCount, actionType").Where("rid = ?").Prepare(),
create: acc.Insert(re).Columns("tid, content, parsed_content, createdAt, lastUpdated, ipaddress, words, createdBy").Fields("?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?").Prepare(),
get: acc.Select(re).Columns("tid, content, createdBy, createdAt, lastEdit, lastEditBy, ip, likeCount, attachCount, actionType").Where("rid=?").Prepare(),
getAll: acc.Select(re).Columns("rid,tid, content, createdBy, createdAt, lastEdit, lastEditBy, ip, likeCount, attachCount, actionType").Prepare(),
exists: acc.Exists(re, "rid").Prepare(),
create: acc.Insert(re).Columns("tid, content, parsed_content, createdAt, lastUpdated, ip, words, createdBy").Fields("?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?").Prepare(),
count: acc.Count(re).Prepare(),
countUser: acc.Count(re).Where("createdBy=?").Prepare(),
countWordUser: acc.Count(re).Where("createdBy=? AND words>=?").Prepare(),
}, acc.FirstError()
}
@ -53,8 +66,38 @@ func (s *SQLReplyStore) Get(id int) (*Reply, error) {
return r, err
}
/*func (s *SQLReplyStore) eachr(f func(*sql.Rows) error) error {
return eachall(s.getAll, f)
}*/
func (s *SQLReplyStore) Each(f func(*Reply) error) error {
rows, err := s.getAll.Query()
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
r := new(Reply)
if err := rows.Scan(&r.ID, &r.ParentID, &r.Content, &r.CreatedBy, &r.CreatedAt, &r.LastEdit, &r.LastEditBy, &r.IP, &r.LikeCount, &r.AttachCount, &r.ActionType); err != nil {
return err
}
if err := f(r); err != nil {
return err
}
}
return rows.Err()
}
func (s *SQLReplyStore) Exists(id int) bool {
err := s.exists.QueryRow(id).Scan(&id)
if err != nil && err != ErrNoRows {
LogError(err)
}
return err != ErrNoRows
}
// TODO: Write a test for this
func (s *SQLReplyStore) Create(t *Topic, content string, ip string, uid int) (rid int, err error) {
func (s *SQLReplyStore) Create(t *Topic, content, ip string, uid int) (rid int, err error) {
if Config.DisablePostIP {
ip = "0"
}
@ -74,11 +117,16 @@ func (s *SQLReplyStore) Create(t *Topic, content string, ip string, uid int) (ri
// TODO: Write a test for this
// Count returns the total number of topic replies on these forums
func (s *SQLReplyStore) Count() (count int) {
err := s.count.QueryRow().Scan(&count)
if err != nil {
LogError(err)
}
return count
return Countf(s.count)
}
func (s *SQLReplyStore) CountUser(uid int) (count int) {
return Countf(s.countUser, uid)
}
func (s *SQLReplyStore) CountMegaUser(uid int) (count int) {
return Countf(s.countWordUser, uid, SettingBox.Load().(SettingMap)["megapost_min_words"].(int))
}
func (s *SQLReplyStore) CountBigUser(uid int) (count int) {
return Countf(s.countWordUser, uid, SettingBox.Load().(SettingMap)["bigpost_min_words"].(int))
}
func (s *SQLReplyStore) SetCache(cache ReplyCache) {

View File

@ -17,7 +17,7 @@ var ErrAlreadyReported = errors.New("This item has already been reported")
// The report system mostly wraps around the topic system for simplicty
type ReportStore interface {
Create(title string, content string, user *User, itemType string, itemID int) (int, error)
Create(title, content string, user *User, itemType string, itemID int) (int, error)
}
type DefaultReportStore struct {
@ -28,13 +28,13 @@ type DefaultReportStore struct {
func NewDefaultReportStore(acc *qgen.Accumulator) (*DefaultReportStore, error) {
t := "topics"
return &DefaultReportStore{
create: acc.Insert(t).Columns("title, content, parsed_content, ipaddress, createdAt, lastReplyAt, createdBy, lastReplyBy, data, parentID, css_class").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,?,'report'").Prepare(),
exists: acc.Count(t).Where("data = ? AND data != '' AND parentID = ?").Prepare(),
create: acc.Insert(t).Columns("title, content, parsed_content, ip, createdAt, lastReplyAt, createdBy, lastReplyBy, data, parentID, css_class").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,?,'report'").Prepare(),
exists: acc.Count(t).Where("data=? AND data!='' AND parentID=?").Prepare(),
}, acc.FirstError()
}
// ! There's a data race in this. If two users report one item at the exact same time, then both reports will go through
func (s *DefaultReportStore) Create(title string, content string, u *User, itemType string, itemID int) (tid int, err error) {
func (s *DefaultReportStore) Create(title, content string, u *User, itemType string, itemID int) (tid int, err error) {
var count int
err = s.exists.QueryRow(itemType+"_"+strconv.Itoa(itemID), ReportForumID).Scan(&count)
if err != nil && err != sql.ErrNoRows {

View File

@ -10,21 +10,39 @@ var Subscriptions SubscriptionStore
// ? Should we have a subscription store for each zone? topic, forum, etc?
type SubscriptionStore interface {
Add(uid int, elementID int, elementType string) error
Add(uid, elementID int, elementType string) error
Delete(uid, targetID int, targetType string) error
DeleteResource(targetID int, targetType string) error
}
type DefaultSubscriptionStore struct {
add *sql.Stmt
delete *sql.Stmt
deleteResource *sql.Stmt
}
func NewDefaultSubscriptionStore() (*DefaultSubscriptionStore, error) {
acc := qgen.NewAcc()
ast := "activity_subscriptions"
return &DefaultSubscriptionStore{
add: acc.Insert("activity_subscriptions").Columns("user, targetID, targetType, level").Fields("?,?,?,2").Prepare(),
add: acc.Insert(ast).Columns("user, targetID, targetType, level").Fields("?,?,?,2").Prepare(),
delete: acc.Delete(ast).Where("user=? AND targetID=? AND targetType=?").Prepare(),
deleteResource: acc.Delete(ast).Where("targetID=? AND targetType=?").Prepare(),
}, acc.FirstError()
}
func (s *DefaultSubscriptionStore) Add(uid int, elementID int, elementType string) error {
func (s *DefaultSubscriptionStore) Add(uid, elementID int, elementType string) error {
_, err := s.add.Exec(uid, elementID, elementType)
return err
}
// TODO: Add a primary key to the activity subscriptions table
func (s *DefaultSubscriptionStore) Delete(uid, targetID int, targetType string) error {
_, err := s.delete.Exec(uid, targetID, targetType)
return err
}
func (s *DefaultSubscriptionStore) DeleteResource(targetID int, targetType string) error {
_, err := s.deleteResource.Exec(targetID, targetType)
return err
}

View File

@ -15,7 +15,7 @@ import (
"sync"
"sync/atomic"
"github.com/Azareal/Gosora/query_gen"
qgen "github.com/Azareal/Gosora/query_gen"
)
// TODO: Something more thread-safe
@ -44,8 +44,8 @@ func init() {
t := "themes"
themeStmts = ThemeStmts{
getAll: acc.Select(t).Columns("uname,default").Prepare(),
isDefault: acc.Select(t).Columns("default").Where("uname = ?").Prepare(),
update: acc.Update(t).Set("default = ?").Where("uname = ?").Prepare(),
isDefault: acc.Select(t).Columns("default").Where("uname=?").Prepare(),
update: acc.Update(t).Set("default=?").Where("uname=?").Prepare(),
add: acc.Insert(t).Columns("uname,default").Fields("?,?").Prepare(),
}
return acc.FirstError()

View File

@ -31,12 +31,12 @@ func ThumbTask(thumbChan chan bool) {
// Has the avatar been removed or already been processed by the thumbnailer?
if len(u.RawAvatar) < 2 || u.RawAvatar[1] == '.' {
_, _ = acc.Delete("users_avatar_queue").Where("uid = ?").Run(uid)
_, _ = acc.Delete("users_avatar_queue").Where("uid=?").Run(uid)
return nil
}
_, err = os.Stat("./uploads/avatar_" + strconv.Itoa(u.ID) + u.RawAvatar)
if os.IsNotExist(err) {
_, _ = acc.Delete("users_avatar_queue").Where("uid = ?").Run(uid)
_, _ = acc.Delete("users_avatar_queue").Where("uid=?").Run(uid)
return nil
} else if err != nil {
return errors.WithStack(err)
@ -63,7 +63,7 @@ func ThumbTask(thumbChan chan bool) {
if err != nil {
return errors.WithStack(err)
}
_, err = acc.Delete("users_avatar_queue").Where("uid = ?").Run(uid)
_, err = acc.Delete("users_avatar_queue").Where("uid=?").Run(uid)
return errors.WithStack(err)
})
if err != nil {
@ -86,18 +86,18 @@ func ThumbTask(thumbChan chan bool) {
var Thumbnailer ThumbnailerInt
type ThumbnailerInt interface {
Resize(format string, inPath string, tmpPath string, outPath string, width int) error
Resize(format, inPath, tmpPath, outPath string, width int) error
}
type RezThumbnailer struct {
}
func (thumb *RezThumbnailer) Resize(format string, inPath string, tmpPath string, outPath string, width int) error {
func (thumb *RezThumbnailer) Resize(format, inPath, tmpPath, outPath string, width int) error {
// TODO: Sniff the aspect ratio of the image and calculate the dest height accordingly, bug make sure it isn't excessively high
return nil
}
func (thumb *RezThumbnailer) resize(format string, inPath string, outPath string, width int, height int) error {
func (thumb *RezThumbnailer) resize(format, inPath, outPath string, width, height int) error {
return nil
}
@ -109,7 +109,7 @@ func NewCaireThumbnailer() *CaireThumbnailer {
return &CaireThumbnailer{}
}
func precodeImage(format string, inPath string, tmpPath string) error {
func precodeImage(format, inPath, tmpPath string) error {
imageFile, err := os.Open(inPath)
if err != nil {
return err
@ -139,7 +139,7 @@ func precodeImage(format string, inPath string, tmpPath string) error {
return jpeg.Encode(outFile, img, nil)
}
func (thumb *CaireThumbnailer) Resize(format string, inPath string, tmpPath string, outPath string, width int) error {
func (thumb *CaireThumbnailer) Resize(format, inPath, tmpPath, outPath string, width int) error {
err := precodeImage(format, inPath, tmpPath)
if err != nil {
return err

View File

@ -15,6 +15,8 @@ import (
"strings"
"time"
//"log"
p "github.com/Azareal/Gosora/common/phrases"
qgen "github.com/Azareal/Gosora/query_gen"
)
@ -197,9 +199,9 @@ type TopicStmts struct {
createLike *sql.Stmt
addLikesToTopic *sql.Stmt
delete *sql.Stmt
deleteReplies *sql.Stmt
deleteLikesForTopic *sql.Stmt
deleteActivity *sql.Stmt
deleteActivitySubs *sql.Stmt
edit *sql.Stmt
setPoll *sql.Stmt
createAction *sql.Stmt
@ -215,8 +217,8 @@ func init() {
t := "topics"
topicStmts = TopicStmts{
getRids: acc.Select("replies").Columns("rid").Where("tid = ?").Orderby("rid ASC").Limit("?,?").Prepare(),
getReplies: acc.SimpleLeftJoin("replies AS r", "users AS u", "r.rid, r.content, r.createdBy, r.createdAt, r.lastEdit, r.lastEditBy, u.avatar, u.name, u.group, u.level, r.ipaddress, r.likeCount, r.attachCount, r.actionType", "r.createdBy = u.uid", "r.tid = ?", "r.rid ASC", "?,?"),
addReplies: acc.Update(t).Set("postCount = postCount + ?, lastReplyBy = ?, lastReplyAt = UTC_TIMESTAMP()").Where("tid = ?").Prepare(),
getReplies: acc.SimpleLeftJoin("replies AS r", "users AS u", "r.rid, r.content, r.createdBy, r.createdAt, r.lastEdit, r.lastEditBy, u.avatar, u.name, u.group, u.level, r.ip, r.likeCount, r.attachCount, r.actionType", "r.createdBy = u.uid", "r.tid = ?", "r.rid ASC", "?,?"),
addReplies: acc.Update(t).Set("postCount=postCount+?, lastReplyBy=?, lastReplyAt=UTC_TIMESTAMP()").Where("tid=?").Prepare(),
updateLastReply: acc.Update(t).Set("lastReplyID=?").Where("lastReplyID > ? AND tid = ?").Prepare(),
lock: acc.Update(t).Set("is_closed=1").Where("tid=?").Prepare(),
unlock: acc.Update(t).Set("is_closed=0").Where("tid=?").Prepare(),
@ -225,17 +227,17 @@ func init() {
unstick: acc.Update(t).Set("sticky=0").Where("tid=?").Prepare(),
hasLikedTopic: acc.Select("likes").Columns("targetItem").Where("sentBy=? and targetItem=? and targetType='topics'").Prepare(),
createLike: acc.Insert("likes").Columns("weight, targetItem, targetType, sentBy, createdAt").Fields("?,?,?,?,UTC_TIMESTAMP()").Prepare(),
addLikesToTopic: acc.Update(t).Set("likeCount=likeCount+?").Where("tid = ?").Prepare(),
addLikesToTopic: acc.Update(t).Set("likeCount=likeCount+?").Where("tid=?").Prepare(),
delete: acc.Delete(t).Where("tid=?").Prepare(),
deleteReplies: acc.Delete("replies").Where("tid=?").Prepare(),
deleteLikesForTopic: acc.Delete("likes").Where("targetItem=? AND targetType='topics'").Prepare(),
deleteActivity: acc.Delete("activity_stream").Where("elementID=? AND elementType='topic'").Prepare(),
deleteActivitySubs: acc.Delete("activity_subscriptions").Where("targetID=? AND targetType='topic'").Prepare(),
edit: acc.Update(t).Set("title=?,content=?,parsed_content=?").Where("tid=?").Prepare(), // TODO: Only run the content update bits on non-polls, does this matter?
setPoll: acc.Update(t).Set("poll=?").Where("tid=? AND poll=0").Prepare(),
createAction: acc.Insert("replies").Columns("tid, actionType, ipaddress, createdBy, createdAt, lastUpdated, content, parsed_content").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),'',''").Prepare(),
createAction: acc.Insert("replies").Columns("tid, actionType, ip, createdBy, createdAt, lastUpdated, content, parsed_content").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),'',''").Prepare(),
getTopicUser: acc.SimpleLeftJoin("topics AS t", "users AS u", "t.title, t.content, t.createdBy, t.createdAt, t.lastReplyAt, t.lastReplyBy, t.lastReplyID, t.is_closed, t.sticky, t.parentID, t.ipaddress, t.views, t.postCount, t.likeCount, t.attachCount,t.poll, u.name, u.avatar, u.group, u.level", "t.createdBy = u.uid", "tid = ?", "", ""),
getByReplyID: acc.SimpleLeftJoin("replies AS r", "topics AS t", "t.tid, t.title, t.content, t.createdBy, t.createdAt, t.is_closed, t.sticky, t.parentID, t.ipaddress, t.views, t.postCount, t.likeCount, t.poll, t.data", "r.tid = t.tid", "rid = ?", "", ""),
getTopicUser: acc.SimpleLeftJoin("topics AS t", "users AS u", "t.title, t.content, t.createdBy, t.createdAt, t.lastReplyAt, t.lastReplyBy, t.lastReplyID, t.is_closed, t.sticky, t.parentID, t.ip, t.views, t.postCount, t.likeCount, t.attachCount,t.poll, u.name, u.avatar, u.group, u.level", "t.createdBy=u.uid", "tid=?", "", ""),
getByReplyID: acc.SimpleLeftJoin("replies AS r", "topics AS t", "t.tid, t.title, t.content, t.createdBy, t.createdAt, t.is_closed, t.sticky, t.parentID, t.ip, t.views, t.postCount, t.likeCount, t.poll, t.data", "r.tid=t.tid", "rid=?", "", ""),
}
return acc.FirstError()
})
@ -322,8 +324,17 @@ func (t *Topic) Like(score, uid int) (err error) {
return err
}
// TODO: Implement this
// TODO: Use a transaction
func (t *Topic) Unlike(uid int) error {
err := Likes.Delete(t.ID,"topics")
if err != nil {
return err
}
_, err = topicStmts.addLikesToTopic.Exec(-1, t.ID)
if err != nil {
return err
}
_, err = userStmts.decLiked.Exec(1, uid)
t.cacheRemove()
return nil
}
@ -345,14 +356,29 @@ func handleLikedTopicReplies(tid int) error {
if err != nil {
return err
}
err = Activity.DeleteByParams("like", rid, "post")
if err != nil {
return err
}
}
return rows.Err()
}
func handleTopicAttachments(tid int) error {
f := func(stmt *sql.Stmt) error {
rows, err := stmt.Query(tid)
err := handleAttachments(userStmts.getAttachmentsOfTopic, tid)
if err != nil {
return err
}
return handleAttachments(userStmts.getAttachmentsOfTopic2, tid)
}
func handleReplyAttachments(rid int) error {
return handleAttachments(replyStmts.getAidsOfReply, rid)
}
func handleAttachments(stmt *sql.Stmt, id int) error {
rows, err := stmt.Query(id)
if err != nil {
return err
}
@ -371,17 +397,31 @@ func handleTopicAttachments(tid int) error {
}
return rows.Err()
}
err := f(userStmts.getAttachmentsOfTopic)
}
// TODO: Only load a row per createdBy, maybe with group by?
func handleTopicReplies(umap map[int]struct{}, uid int, tid int) error {
rows, err := userStmts.getRepliesOfTopic.Query(uid, tid)
if err != nil {
return err
}
return f(userStmts.getAttachmentsOfTopic2)
defer rows.Close()
for rows.Next() {
var createdBy int
err := rows.Scan(&createdBy)
if err != nil {
return err
}
umap[createdBy] = struct{}{}
}
return rows.Err()
}
// TODO: Use a transaction here
func (t *Topic) Delete() error {
creator, err := Users.Get(t.CreatedBy)
/*creator, err := Users.Get(t.CreatedBy)
if err == nil {
err = creator.DecreasePostStats(WordCount(t.Content), true)
if err != nil {
@ -389,10 +429,10 @@ func (t *Topic) Delete() error {
}
} else if err != ErrNoRows {
return err
}
}*/
// TODO: Clear reply cache too
_, err = topicStmts.delete.Exec(t.ID)
_, err := topicStmts.delete.Exec(t.ID)
t.cacheRemove()
if err != nil {
return err
@ -405,15 +445,38 @@ func (t *Topic) Delete() error {
if err != nil {
return err
}
if t.PostCount > 1 {
err = handleLikedTopicReplies(t.ID)
if err != nil {
return err
}
umap := make(map[int]struct{})
err = handleTopicReplies(umap, t.CreatedBy, t.ID)
if err != nil {
return err
}
_, err = topicStmts.deleteReplies.Exec(t.ID)
if err != nil {
return err
}
for uid := range umap {
err = (&User{ID: uid}).RecalcPostStats()
if err != nil {
//log.Printf("err: %+v\n", err)
return err
}
}
}
err = (&User{ID: t.CreatedBy}).RecalcPostStats()
if err != nil {
return err
}
err = handleTopicAttachments(t.ID)
if err != nil {
return err
}
_, err = topicStmts.deleteActivitySubs.Exec(t.ID)
err = Subscriptions.DeleteResource(t.ID, "topic")
if err != nil {
return err
}

View File

@ -29,13 +29,16 @@ type TopicStore interface {
BypassGet(id int) (*Topic, error)
BulkGetMap(ids []int) (list map[int]*Topic, err error)
Exists(id int) bool
Create(fid int, name string, content string, uid int, ip string) (tid int, err error)
Create(fid int, name, content string, uid int, ip 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)
Count() int
CountUser(uid int) int
CountMegaUser(uid int) int
CountBigUser(uid int) int
SetCache(cache TopicCache)
GetCache() TopicCache
@ -47,6 +50,8 @@ type DefaultTopicStore struct {
get *sql.Stmt
exists *sql.Stmt
count *sql.Stmt
countUser *sql.Stmt
countWordUser *sql.Stmt
create *sql.Stmt
}
@ -59,37 +64,39 @@ func NewDefaultTopicStore(cache TopicCache) (*DefaultTopicStore, error) {
t := "topics"
return &DefaultTopicStore{
cache: cache,
get: acc.Select(t).Columns("title, content, createdBy, createdAt, lastReplyBy, lastReplyAt, lastReplyID, is_closed, sticky, parentID, ipaddress, views, postCount, likeCount, attachCount, poll, data").Where("tid = ?").Prepare(),
get: acc.Select(t).Columns("title, content, createdBy, createdAt, lastReplyBy, lastReplyAt, lastReplyID, is_closed, sticky, parentID, ip, views, postCount, likeCount, attachCount, poll, data").Where("tid=?").Prepare(),
exists: acc.Exists(t, "tid").Prepare(),
count: acc.Count(t).Prepare(),
create: acc.Insert(t).Columns("parentID, title, content, parsed_content, createdAt, lastReplyAt, lastReplyBy, ipaddress, words, createdBy").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,?").Prepare(),
countUser: acc.Count(t).Where("createdBy=?").Prepare(),
countWordUser: acc.Count(t).Where("createdBy=? AND words>=?").Prepare(),
create: acc.Insert(t).Columns("parentID, title, content, parsed_content, createdAt, lastReplyAt, lastReplyBy, ip, words, createdBy").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,?").Prepare(),
}, acc.FirstError()
}
func (s *DefaultTopicStore) DirtyGet(id int) *Topic {
topic, err := s.cache.Get(id)
t, err := s.cache.Get(id)
if err == nil {
return topic
return t
}
topic, err = s.BypassGet(id)
t, err = s.BypassGet(id)
if err == nil {
_ = s.cache.Set(topic)
return topic
_ = s.cache.Set(t)
return t
}
return BlankTopic()
}
// TODO: Log weird cache errors?
func (s *DefaultTopicStore) Get(id int) (topic *Topic, err error) {
topic, err = s.cache.Get(id)
func (s *DefaultTopicStore) Get(id int) (t *Topic, err error) {
t, err = s.cache.Get(id)
if err == nil {
return topic, nil
return t, nil
}
topic, err = s.BypassGet(id)
t, err = s.BypassGet(id)
if err == nil {
_ = s.cache.Set(topic)
_ = s.cache.Set(t)
}
return topic, err
return t, err
}
// BypassGet will always bypass the cache and pull the topic directly from the database
@ -102,6 +109,15 @@ func (s *DefaultTopicStore) BypassGet(id int) (*Topic, error) {
return t, err
}
/*func (s *DefaultTopicStore) GetByUser(uid int) (list map[int]*Topic, err error) {
t := &Topic{ID: id}
err := s.get.QueryRow(id).Scan(&t.Title, &t.Content, &t.CreatedBy, &t.CreatedAt, &t.LastReplyBy, &t.LastReplyAt, &t.LastReplyID, &t.IsClosed, &t.Sticky, &t.ParentID, &t.IP, &t.ViewCount, &t.PostCount, &t.LikeCount, &t.AttachCount, &t.Poll, &t.Data)
if err == nil {
t.Link = BuildTopicURL(NameToSlug(t.Title), id)
}
return t, err
}*/
// TODO: Avoid duplicating much of this logic from user_store.go
func (s *DefaultTopicStore) BulkGetMap(ids []int) (list map[int]*Topic, err error) {
idCount := len(ids)
@ -144,7 +160,7 @@ func (s *DefaultTopicStore) BulkGetMap(ids []int) (list map[int]*Topic, err erro
}
q = q[0 : len(q)-1]
rows, err := qgen.NewAcc().Select("topics").Columns("tid,title,content,createdBy,createdAt,lastReplyBy,lastReplyAt,lastReplyID,is_closed,sticky,parentID,ipaddress,views,postCount,likeCount,attachCount,poll,data").Where("tid IN(" + q + ")").Query(idList...)
rows, err := qgen.NewAcc().Select("topics").Columns("tid,title,content,createdBy,createdAt,lastReplyBy,lastReplyAt,lastReplyID,is_closed,sticky,parentID,ip,views,postCount,likeCount,attachCount,poll,data").Where("tid IN(" + q + ")").Query(idList...)
if err != nil {
return list, err
}
@ -197,7 +213,7 @@ func (s *DefaultTopicStore) Exists(id int) bool {
return s.exists.QueryRow(id).Scan(&id) == nil
}
func (s *DefaultTopicStore) Create(fid int, name string, content string, uid int, ip string) (tid int, err error) {
func (s *DefaultTopicStore) Create(fid int, name, content string, uid int, ip string) (tid int, err error) {
if name == "" {
return 0, ErrNoTitle
}
@ -236,11 +252,16 @@ func (s *DefaultTopicStore) AddLastTopic(t *Topic, fid int) error {
// Count returns the total number of topics on these forums
func (s *DefaultTopicStore) Count() (count int) {
err := s.count.QueryRow().Scan(&count)
if err != nil {
LogError(err)
}
return count
return Countf(s.count)
}
func (s *DefaultTopicStore) CountUser(uid int) (count int) {
return Countf(s.countUser, uid)
}
func (s *DefaultTopicStore) CountMegaUser(uid int) (count int) {
return Countf(s.countWordUser, uid, SettingBox.Load().(SettingMap)["megapost_min_words"].(int))
}
func (s *DefaultTopicStore) CountBigUser(uid int) (count int) {
return Countf(s.countWordUser, uid, SettingBox.Load().(SettingMap)["bigpost_min_words"].(int))
}
func (s *DefaultTopicStore) SetCache(cache TopicCache) {

View File

@ -12,6 +12,7 @@ import (
"strconv"
"strings"
"time"
//"log"
qgen "github.com/Azareal/Gosora/query_gen"
"github.com/go-sql-driver/mysql"
@ -137,6 +138,7 @@ type UserStmts struct {
incTopics *sql.Stmt
updateLevel *sql.Stmt
resetStats *sql.Stmt
setStats *sql.Stmt
decLiked *sql.Stmt
updateLastIP *sql.Stmt
@ -152,6 +154,7 @@ type UserStmts struct {
getLikedRepliesOfTopic *sql.Stmt
getAttachmentsOfTopic *sql.Stmt
getAttachmentsOfTopic2 *sql.Stmt
getRepliesOfTopic *sql.Stmt
}
var userStmts UserStmts
@ -168,6 +171,8 @@ func init() {
setName: acc.Update(u).Set("name=?").Where(w).Prepare(),
update: acc.Update(u).Set("name=?,email=?,group=?").Where(w).Prepare(), // TODO: Implement user_count for users_groups on things which use this
// Stat Statements
// TODO: Do +0 to avoid having as many statements?
incScore: acc.Update(u).Set("score=score+?").Where(w).Prepare(),
incPosts: acc.Update(u).Set("posts=posts+?").Where(w).Prepare(),
incBigposts: acc.Update(u).Set("posts=posts+?,bigposts=bigposts+?").Where(w).Prepare(),
@ -178,6 +183,7 @@ func init() {
incTopics: acc.SimpleUpdate(u, "topics=topics+?", w),
updateLevel: acc.SimpleUpdate(u, "level=?", w),
resetStats: acc.Update(u).Set("score=0,posts=0,bigposts=0,megaposts=0,topics=0,level=0").Where(w).Prepare(),
setStats: acc.Update(u).Set("score=?,posts=?,bigposts=?,megaposts=?,topics=?,level=?").Where(w).Prepare(),
incLiked: acc.Update(u).Set("liked=liked+?,lastLiked=UTC_TIMESTAMP()").Where(w).Prepare(),
decLiked: acc.Update(u).Set("liked=liked-?").Where(w).Prepare(),
@ -189,12 +195,14 @@ func init() {
scheduleAvatarResize: acc.Insert("users_avatar_queue").Columns("uid").Fields("?").Prepare(),
deletePosts: acc.Select("topics").Columns("tid,parentID,poll").Where("createdBy=?").Prepare(),
deleteProfilePosts: acc.Select("users_replies").Columns("rid").Where("createdBy=?").Prepare(),
// Delete All Posts Statements
deletePosts: acc.Select("topics").Columns("tid,parentID,postCount,poll").Where("createdBy=?").Prepare(),
deleteProfilePosts: acc.Select("users_replies").Columns("rid,uid").Where("createdBy=?").Prepare(),
deleteReplyPosts: acc.Select("replies").Columns("rid,tid").Where("createdBy=?").Prepare(),
getLikedRepliesOfTopic: acc.Select("replies").Columns("rid").Where("tid=? AND likeCount>0").Prepare(),
getAttachmentsOfTopic: acc.Select("attachments").Columns("attachID").Where("originID=? AND originTable='topics'").Prepare(),
getAttachmentsOfTopic2: acc.Select("attachments").Columns("attachID").Where("extra=? AND originTable='replies'").Prepare(),
getRepliesOfTopic: acc.Select("replies").Columns("words").Where("createdBy!=? AND tid=?").Prepare(),
}
return acc.FirstError()
})
@ -325,6 +333,7 @@ func (u *User) Delete() error {
return nil
}
// TODO: dismiss-event
func (u *User) DeletePosts() error {
rows, err := userStmts.deletePosts.Query(u.ID)
if err != nil {
@ -336,9 +345,10 @@ func (u *User) DeletePosts() error {
updatedForums := make(map[int]int) // forum[count]
tc := Topics.GetCache()
umap := make(map[int]struct{})
for rows.Next() {
var tid, parentID, poll int
err := rows.Scan(&tid, &parentID, &poll)
var tid, parentID, postCount,poll int
err := rows.Scan(&tid, &parentID, &postCount, &poll)
if err != nil {
return err
}
@ -356,15 +366,25 @@ func (u *User) DeletePosts() error {
if err != nil {
return err
}
err = handleLikedTopicReplies(tid)
if err != nil {
return err
}
err = handleTopicAttachments(tid)
if err != nil {
return err
}
_, err = topicStmts.deleteActivitySubs.Exec(tid)
if postCount > 1 {
err = handleLikedTopicReplies(tid)
if err != nil {
return err
}
err = handleTopicReplies(umap, u.ID, tid)
if err != nil {
return err
}
_, err = topicStmts.deleteReplies.Exec(tid)
if err != nil {
return err
}
}
err = Subscriptions.DeleteResource(tid,"topic")
if err != nil {
return err
}
@ -386,6 +406,12 @@ func (u *User) DeletePosts() error {
if err != nil {
return err
}
for uid, _ := range umap {
err = (&User{ID: uid}).RecalcPostStats()
if err != nil {
return err
}
}
for fid, count := range updatedForums {
err := Forums.RemoveTopics(fid, count)
if err != nil && err != ErrNoRows {
@ -400,8 +426,8 @@ func (u *User) DeletePosts() error {
defer rows.Close()
for rows.Next() {
var rid int
err := rows.Scan(&rid)
var rid, uid int
err := rows.Scan(&rid,&uid)
if err != nil {
return err
}
@ -409,7 +435,12 @@ func (u *User) DeletePosts() error {
if err != nil {
return err
}
// TODO: Remove alerts.
// TODO: Optimise this
// TODO: dismiss-event
err = Activity.DeleteByParamsExtra("reply",uid,"user",strconv.Itoa(rid))
if err != nil {
return err
}
}
if err = rows.Err(); err != nil {
return err
@ -454,6 +485,10 @@ func (u *User) DeletePosts() error {
if err != nil {
return err
}
err = Activity.DeleteByParamsExtra("reply",tid,"topic",strconv.Itoa(rid))
if err != nil {
return err
}
_, err = replyStmts.deleteActivitySubs.Exec(rid)
if err != nil {
return err
@ -561,6 +596,42 @@ func (u *User) IncreasePostStats(wcount int, topic bool) (err error) {
return err
}
func (u *User) countf(stmt *sql.Stmt) (count int) {
err := stmt.QueryRow().Scan(&count)
if err != nil {
LogError(err)
}
return count
}
func (u *User) RecalcPostStats() error {
var score int
tcount := Topics.CountUser(u.ID)
rcount := Rstore.CountUser(u.ID)
//log.Print("tcount:", tcount)
//log.Print("rcount:", rcount)
score += tcount * 2
score += rcount
var tmega, tbig, rmega, rbig int
if tcount > 0 {
tmega = Topics.CountMegaUser(u.ID)
score += tmega * 3
tbig := Topics.CountBigUser(u.ID)
score += tbig
}
if rcount > 0 {
rmega = Rstore.CountMegaUser(u.ID)
score += rmega * 3
rbig = Rstore.CountBigUser(u.ID)
score += rbig
}
_, err := userStmts.setStats.Exec(score, tcount+rcount, tbig+rbig, tmega+rmega, tcount, GetLevel(score), u.ID)
u.CacheRemove()
return err
}
func (u *User) DecreasePostStats(wcount int, topic bool) (err error) {
baseScore := -1
if topic {

View File

@ -21,6 +21,7 @@ type UserStore interface {
GetByName(name string) (*User, error)
Exists(id int) bool
GetOffset(offset, perPage int) ([]*User, error)
Each(f func(*User) error) error
//BulkGet(ids []int) ([]*User, error)
BulkGetMap(ids []int) (map[int]*User, error)
BypassGet(id int) (*User, error)
@ -38,6 +39,7 @@ type DefaultUserStore struct {
get *sql.Stmt
getByName *sql.Stmt
getOffset *sql.Stmt
getAll *sql.Stmt
exists *sql.Stmt
register *sql.Stmt
nameExists *sql.Stmt
@ -57,6 +59,7 @@ func NewDefaultUserStore(cache UserCache) (*DefaultUserStore, error) {
get: acc.Select(u).Columns("name, group, active, is_super_admin, session, email, avatar, message, level, score, posts, liked, last_ip, temp_group, enable_embeds").Where("uid = ?").Prepare(),
getByName: acc.Select(u).Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, level, score, posts, liked, last_ip, temp_group, enable_embeds").Where("name = ?").Prepare(),
getOffset: acc.Select(u).Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, level, score, posts, liked, last_ip, temp_group, enable_embeds").Orderby("uid ASC").Limit("?,?").Prepare(),
getAll: acc.Select(u).Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, level, score, posts, liked, last_ip, temp_group, enable_embeds").Prepare(),
exists: acc.Exists(u, "uid").Prepare(),
register: acc.Insert(u).Columns("name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt, lastLiked, oldestItemLikedCreatedAt").Fields("?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(), // TODO: Implement user_count on users_groups here
nameExists: acc.Exists(u, "name").Prepare(),
@ -143,6 +146,29 @@ func (s *DefaultUserStore) GetOffset(offset, perPage int) (users []*User, err er
}
return users, rows.Err()
}
func (s *DefaultUserStore) Each(f func(*User) error) error {
rows, err := s.getAll.Query()
if err != nil {
return err
}
defer rows.Close()
var embeds int
for rows.Next() {
u := new(User)
if err := rows.Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &embeds); err != nil {
return err
}
if embeds != -1 {
u.ParseSettings = DefaultParseSettings.CopyPtr()
u.ParseSettings.NoEmbed = embeds == 0
}
u.Init()
if err := f(u); err != nil {
return err
}
}
return rows.Err()
}
// TODO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts?
// TODO: ID of 0 should always error?
@ -265,7 +291,7 @@ func (s *DefaultUserStore) Exists(id int) bool {
// TODO: Change active to a bool?
// TODO: Use unique keys for the usernames
func (s *DefaultUserStore) Create(name string, password string, email string, group int, active bool) (int, error) {
func (s *DefaultUserStore) Create(name, password, email string, group int, active bool) (int, error) {
// TODO: Strip spaces?
// ? 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?
@ -297,11 +323,7 @@ func (s *DefaultUserStore) Create(name string, password string, email string, gr
// Count returns the total number of users registered on the forums
func (s *DefaultUserStore) Count() (count int) {
err := s.count.QueryRow().Scan(&count)
if err != nil {
LogError(err)
}
return count
return Countf(s.count)
}
func (s *DefaultUserStore) SetCache(cache UserCache) {

View File

@ -3,11 +3,11 @@ package common
import (
"database/sql"
"encoding/json"
"strings"
"strconv"
"strings"
"sync/atomic"
"github.com/Azareal/Gosora/query_gen"
qgen "github.com/Azareal/Gosora/query_gen"
)
type WidgetStmts struct {
@ -29,9 +29,9 @@ func init() {
//getList: acc.Select(w).Columns("wid, position, side, type, active, location, data").Orderby("position ASC").Prepare(),
getDockList: acc.Select(w).Columns("wid, position, type, active, location, data").Where("side = ?").Orderby("position ASC").Prepare(),
//model: acc.SimpleModel(w,"position,type,active,location,data","wid"),
delete: acc.Delete(w).Where("wid = ?").Prepare(),
delete: acc.Delete(w).Where("wid=?").Prepare(),
create: acc.Insert(w).Columns("position, side, type, active, location, data").Fields("?,?,?,?,?,?").Prepare(),
update: acc.Update(w).Set("position = ?, side = ?, type = ?, active = ?, location = ?, data = ?").Where("wid = ?").Prepare(),
update: acc.Update(w).Set("position=?,side=?,type=?,active=?,location=?,data=?").Where("wid=?").Prepare(),
}
return acc.FirstError()
})
@ -87,7 +87,7 @@ func (w *Widget) Allowed(zone string, zoneid int) bool {
if len(loc) == 0 {
continue
}
sloc := strings.Split(":",loc)
sloc := strings.Split(":", loc)
if len(sloc) > 1 {
iloc, _ := strconv.Atoi(sloc[1])
if zoneid != 0 && iloc != zoneid {

View File

@ -26,9 +26,9 @@ type WordFilterStore interface {
ReloadAll() error
GetAll() (filters map[int]*WordFilter, err error)
Get(id int) (*WordFilter, error)
Create(find string, replace string) (int, error)
Create(find, replace string) (int, error)
Delete(id int) error
Update(id int, find string, replace string) error
Update(id int, find, replace string) error
Length() int
EstCount() int
Count() (count int)
@ -49,10 +49,10 @@ func NewDefaultWordFilterStore(acc *qgen.Accumulator) (*DefaultWordFilterStore,
wf := "word_filters"
store := &DefaultWordFilterStore{
getAll: acc.Select(wf).Columns("wfid,find,replacement").Prepare(),
get: acc.Select(wf).Columns("find,replacement").Where("wfid = ?").Prepare(),
get: acc.Select(wf).Columns("find,replacement").Where("wfid=?").Prepare(),
create: acc.Insert(wf).Columns("find,replacement").Fields("?,?").Prepare(),
delete: acc.Delete(wf).Where("wfid = ?").Prepare(),
update: acc.Update(wf).Set("find = ?, replacement = ?").Where("wfid = ?").Prepare(),
delete: acc.Delete(wf).Where("wfid=?").Prepare(),
update: acc.Update(wf).Set("find=?,replacement=?").Where("wfid=?").Prepare(),
count: acc.Count(wf).Prepare(),
}
// TODO: Should we initialise this elsewhere?
@ -109,7 +109,7 @@ func (s *DefaultWordFilterStore) Get(id int) (*WordFilter, error) {
}
// Create adds a new word filter to the database and refreshes the memory cache
func (s *DefaultWordFilterStore) Create(find string, replace string) (int, error) {
func (s *DefaultWordFilterStore) Create(find, replace string) (int, error) {
res, err := s.create.Exec(find, replace)
if err != nil {
return 0, err
@ -130,7 +130,7 @@ func (s *DefaultWordFilterStore) Delete(id int) error {
return s.ReloadAll()
}
func (s *DefaultWordFilterStore) Update(id int, find string, replace string) error {
func (s *DefaultWordFilterStore) Update(id int, find, replace string) error {
_, err := s.update.Exec(find, replace, id)
if err != nil {
return err

View File

@ -158,6 +158,7 @@ var RouteMap = map[string]interface{}{
"routes.UnlockTopicSubmit": routes.UnlockTopicSubmit,
"routes.MoveTopicSubmit": routes.MoveTopicSubmit,
"routes.LikeTopicSubmit": routes.LikeTopicSubmit,
"routes.UnlikeTopicSubmit": routes.UnlikeTopicSubmit,
"routes.AddAttachToTopicSubmit": routes.AddAttachToTopicSubmit,
"routes.RemoveAttachFromTopicSubmit": routes.RemoveAttachFromTopicSubmit,
"routes.ViewTopic": routes.ViewTopic,
@ -330,39 +331,40 @@ var routeMapEnum = map[string]int{
"routes.UnlockTopicSubmit": 132,
"routes.MoveTopicSubmit": 133,
"routes.LikeTopicSubmit": 134,
"routes.AddAttachToTopicSubmit": 135,
"routes.RemoveAttachFromTopicSubmit": 136,
"routes.ViewTopic": 137,
"routes.CreateReplySubmit": 138,
"routes.ReplyEditSubmit": 139,
"routes.ReplyDeleteSubmit": 140,
"routes.ReplyLikeSubmit": 141,
"routes.AddAttachToReplySubmit": 142,
"routes.RemoveAttachFromReplySubmit": 143,
"routes.ProfileReplyCreateSubmit": 144,
"routes.ProfileReplyEditSubmit": 145,
"routes.ProfileReplyDeleteSubmit": 146,
"routes.PollVote": 147,
"routes.PollResults": 148,
"routes.AccountLogin": 149,
"routes.AccountRegister": 150,
"routes.AccountLogout": 151,
"routes.AccountLoginSubmit": 152,
"routes.AccountLoginMFAVerify": 153,
"routes.AccountLoginMFAVerifySubmit": 154,
"routes.AccountRegisterSubmit": 155,
"routes.AccountPasswordReset": 156,
"routes.AccountPasswordResetSubmit": 157,
"routes.AccountPasswordResetToken": 158,
"routes.AccountPasswordResetTokenSubmit": 159,
"routes.DynamicRoute": 160,
"routes.UploadedFile": 161,
"routes.StaticFile": 162,
"routes.RobotsTxt": 163,
"routes.SitemapXml": 164,
"routes.OpenSearchXml": 165,
"routes.BadRoute": 166,
"routes.HTTPSRedirect": 167,
"routes.UnlikeTopicSubmit": 135,
"routes.AddAttachToTopicSubmit": 136,
"routes.RemoveAttachFromTopicSubmit": 137,
"routes.ViewTopic": 138,
"routes.CreateReplySubmit": 139,
"routes.ReplyEditSubmit": 140,
"routes.ReplyDeleteSubmit": 141,
"routes.ReplyLikeSubmit": 142,
"routes.AddAttachToReplySubmit": 143,
"routes.RemoveAttachFromReplySubmit": 144,
"routes.ProfileReplyCreateSubmit": 145,
"routes.ProfileReplyEditSubmit": 146,
"routes.ProfileReplyDeleteSubmit": 147,
"routes.PollVote": 148,
"routes.PollResults": 149,
"routes.AccountLogin": 150,
"routes.AccountRegister": 151,
"routes.AccountLogout": 152,
"routes.AccountLoginSubmit": 153,
"routes.AccountLoginMFAVerify": 154,
"routes.AccountLoginMFAVerifySubmit": 155,
"routes.AccountRegisterSubmit": 156,
"routes.AccountPasswordReset": 157,
"routes.AccountPasswordResetSubmit": 158,
"routes.AccountPasswordResetToken": 159,
"routes.AccountPasswordResetTokenSubmit": 160,
"routes.DynamicRoute": 161,
"routes.UploadedFile": 162,
"routes.StaticFile": 163,
"routes.RobotsTxt": 164,
"routes.SitemapXml": 165,
"routes.OpenSearchXml": 166,
"routes.BadRoute": 167,
"routes.HTTPSRedirect": 168,
}
var reverseRouteMapEnum = map[int]string{
0: "routes.Overview",
@ -500,39 +502,40 @@ var reverseRouteMapEnum = map[int]string{
132: "routes.UnlockTopicSubmit",
133: "routes.MoveTopicSubmit",
134: "routes.LikeTopicSubmit",
135: "routes.AddAttachToTopicSubmit",
136: "routes.RemoveAttachFromTopicSubmit",
137: "routes.ViewTopic",
138: "routes.CreateReplySubmit",
139: "routes.ReplyEditSubmit",
140: "routes.ReplyDeleteSubmit",
141: "routes.ReplyLikeSubmit",
142: "routes.AddAttachToReplySubmit",
143: "routes.RemoveAttachFromReplySubmit",
144: "routes.ProfileReplyCreateSubmit",
145: "routes.ProfileReplyEditSubmit",
146: "routes.ProfileReplyDeleteSubmit",
147: "routes.PollVote",
148: "routes.PollResults",
149: "routes.AccountLogin",
150: "routes.AccountRegister",
151: "routes.AccountLogout",
152: "routes.AccountLoginSubmit",
153: "routes.AccountLoginMFAVerify",
154: "routes.AccountLoginMFAVerifySubmit",
155: "routes.AccountRegisterSubmit",
156: "routes.AccountPasswordReset",
157: "routes.AccountPasswordResetSubmit",
158: "routes.AccountPasswordResetToken",
159: "routes.AccountPasswordResetTokenSubmit",
160: "routes.DynamicRoute",
161: "routes.UploadedFile",
162: "routes.StaticFile",
163: "routes.RobotsTxt",
164: "routes.SitemapXml",
165: "routes.OpenSearchXml",
166: "routes.BadRoute",
167: "routes.HTTPSRedirect",
135: "routes.UnlikeTopicSubmit",
136: "routes.AddAttachToTopicSubmit",
137: "routes.RemoveAttachFromTopicSubmit",
138: "routes.ViewTopic",
139: "routes.CreateReplySubmit",
140: "routes.ReplyEditSubmit",
141: "routes.ReplyDeleteSubmit",
142: "routes.ReplyLikeSubmit",
143: "routes.AddAttachToReplySubmit",
144: "routes.RemoveAttachFromReplySubmit",
145: "routes.ProfileReplyCreateSubmit",
146: "routes.ProfileReplyEditSubmit",
147: "routes.ProfileReplyDeleteSubmit",
148: "routes.PollVote",
149: "routes.PollResults",
150: "routes.AccountLogin",
151: "routes.AccountRegister",
152: "routes.AccountLogout",
153: "routes.AccountLoginSubmit",
154: "routes.AccountLoginMFAVerify",
155: "routes.AccountLoginMFAVerifySubmit",
156: "routes.AccountRegisterSubmit",
157: "routes.AccountPasswordReset",
158: "routes.AccountPasswordResetSubmit",
159: "routes.AccountPasswordResetToken",
160: "routes.AccountPasswordResetTokenSubmit",
161: "routes.DynamicRoute",
162: "routes.UploadedFile",
163: "routes.StaticFile",
164: "routes.RobotsTxt",
165: "routes.SitemapXml",
166: "routes.OpenSearchXml",
167: "routes.BadRoute",
168: "routes.HTTPSRedirect",
}
var osMapEnum = map[string]int{
"unknown": 0,
@ -690,7 +693,7 @@ type HTTPSRedirect struct {}
func (red *HTTPSRedirect) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Connection", "close")
co.RouteViewCounter.Bump(167)
co.RouteViewCounter.Bump(168)
dest := "https://" + req.Host + req.URL.String()
http.Redirect(w, req, dest, http.StatusTemporaryRedirect)
}
@ -898,7 +901,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
co.GlobalViewCounter.Bump()
if prefix == "/s" { //old prefix: /static
co.RouteViewCounter.Bump(162)
co.RouteViewCounter.Bump(163)
req.URL.Path += extraData
routes.StaticFile(w, req)
return
@ -2274,6 +2277,19 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
co.RouteViewCounter.Bump(134)
err = routes.LikeTopicSubmit(w,req,user,extraData)
case "/topic/unlike/submit/":
err = c.NoSessionMismatch(w,req,user)
if err != nil {
return err
}
err = c.MemberOnly(w,req,user)
if err != nil {
return err
}
co.RouteViewCounter.Bump(135)
err = routes.UnlikeTopicSubmit(w,req,user,extraData)
case "/topic/attach/add/submit/":
err = c.MemberOnly(w,req,user)
if err != nil {
@ -2289,7 +2305,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(135)
co.RouteViewCounter.Bump(136)
err = routes.AddAttachToTopicSubmit(w,req,user,extraData)
case "/topic/attach/remove/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2302,10 +2318,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(136)
co.RouteViewCounter.Bump(137)
err = routes.RemoveAttachFromTopicSubmit(w,req,user,extraData)
default:
co.RouteViewCounter.Bump(137)
co.RouteViewCounter.Bump(138)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -2329,7 +2345,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(138)
co.RouteViewCounter.Bump(139)
err = routes.CreateReplySubmit(w,req,user)
case "/reply/edit/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2342,7 +2358,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(139)
co.RouteViewCounter.Bump(140)
err = routes.ReplyEditSubmit(w,req,user,extraData)
case "/reply/delete/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2355,7 +2371,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(140)
co.RouteViewCounter.Bump(141)
err = routes.ReplyDeleteSubmit(w,req,user,extraData)
case "/reply/like/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2368,7 +2384,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(141)
co.RouteViewCounter.Bump(142)
err = routes.ReplyLikeSubmit(w,req,user,extraData)
case "/reply/attach/add/submit/":
err = c.MemberOnly(w,req,user)
@ -2385,7 +2401,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(142)
co.RouteViewCounter.Bump(143)
err = routes.AddAttachToReplySubmit(w,req,user,extraData)
case "/reply/attach/remove/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2398,7 +2414,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(143)
co.RouteViewCounter.Bump(144)
err = routes.RemoveAttachFromReplySubmit(w,req,user,extraData)
}
case "/profile":
@ -2414,7 +2430,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(144)
co.RouteViewCounter.Bump(145)
err = routes.ProfileReplyCreateSubmit(w,req,user)
case "/profile/reply/edit/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2427,7 +2443,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(145)
co.RouteViewCounter.Bump(146)
err = routes.ProfileReplyEditSubmit(w,req,user,extraData)
case "/profile/reply/delete/submit/":
err = c.NoSessionMismatch(w,req,user)
@ -2440,7 +2456,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(146)
co.RouteViewCounter.Bump(147)
err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData)
}
case "/poll":
@ -2456,23 +2472,23 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(147)
co.RouteViewCounter.Bump(148)
err = routes.PollVote(w,req,user,extraData)
case "/poll/results/":
co.RouteViewCounter.Bump(148)
co.RouteViewCounter.Bump(149)
err = routes.PollResults(w,req,user,extraData)
}
case "/accounts":
switch(req.URL.Path) {
case "/accounts/login/":
co.RouteViewCounter.Bump(149)
co.RouteViewCounter.Bump(150)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
}
err = routes.AccountLogin(w,req,user,head)
case "/accounts/create/":
co.RouteViewCounter.Bump(150)
co.RouteViewCounter.Bump(151)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -2489,7 +2505,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(151)
co.RouteViewCounter.Bump(152)
err = routes.AccountLogout(w,req,user)
case "/accounts/login/submit/":
err = c.ParseForm(w,req,user)
@ -2497,10 +2513,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(152)
co.RouteViewCounter.Bump(153)
err = routes.AccountLoginSubmit(w,req,user)
case "/accounts/mfa_verify/":
co.RouteViewCounter.Bump(153)
co.RouteViewCounter.Bump(154)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -2512,7 +2528,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(154)
co.RouteViewCounter.Bump(155)
err = routes.AccountLoginMFAVerifySubmit(w,req,user)
case "/accounts/create/submit/":
err = c.ParseForm(w,req,user)
@ -2520,10 +2536,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(155)
co.RouteViewCounter.Bump(156)
err = routes.AccountRegisterSubmit(w,req,user)
case "/accounts/password-reset/":
co.RouteViewCounter.Bump(156)
co.RouteViewCounter.Bump(157)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -2535,10 +2551,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(157)
co.RouteViewCounter.Bump(158)
err = routes.AccountPasswordResetSubmit(w,req,user)
case "/accounts/password-reset/token/":
co.RouteViewCounter.Bump(158)
co.RouteViewCounter.Bump(159)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
@ -2550,7 +2566,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err
}
co.RouteViewCounter.Bump(159)
co.RouteViewCounter.Bump(160)
err = routes.AccountPasswordResetTokenSubmit(w,req,user)
}
/*case "/sitemaps": // TODO: Count these views
@ -2567,7 +2583,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
h.Del("Content-Type")
h.Del("Content-Encoding")
}
co.RouteViewCounter.Bump(161)
co.RouteViewCounter.Bump(162)
req.URL.Path += extraData
// TODO: Find a way to propagate errors up from this?
r.UploadHandler(w,req) // TODO: Count these views
@ -2577,7 +2593,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
// TODO: Add support for favicons and robots.txt files
switch(extraData) {
case "robots.txt":
co.RouteViewCounter.Bump(163)
co.RouteViewCounter.Bump(164)
return routes.RobotsTxt(w,req)
case "favicon.ico":
gzw, ok := w.(c.GzipResponseWriter)
@ -2591,10 +2607,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
routes.StaticFile(w,req)
return nil
case "opensearch.xml":
co.RouteViewCounter.Bump(165)
co.RouteViewCounter.Bump(166)
return routes.OpenSearchXml(w,req)
/*case "sitemap.xml":
co.RouteViewCounter.Bump(164)
co.RouteViewCounter.Bump(165)
return routes.SitemapXml(w,req)*/
}
return c.NotFound(w,req,nil)
@ -2605,7 +2621,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
r.RUnlock()
if ok {
co.RouteViewCounter.Bump(160) // TODO: Be more specific about *which* dynamic route it is
co.RouteViewCounter.Bump(161) // TODO: Be more specific about *which* dynamic route it is
req.URL.Path += extraData
return handle(w,req,user)
}
@ -2616,7 +2632,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
} else {
r.DumpRequest(req,"Bad Route")
}
co.RouteViewCounter.Bump(166)
co.RouteViewCounter.Bump(167)
return c.NotFound(w,req,nil)
}
return err

View File

@ -791,7 +791,7 @@ func BenchmarkQueryTopicParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
var tu c.TopicUser
for pb.Next() {
err := db.QueryRow("select topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.views, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.level from topics left join users ON topics.createdBy = users.uid where tid = ?", 1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IP, &tu.ViewCount, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.Level)
err := db.QueryRow("select topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ip, topics.views, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.level from topics left join users ON topics.createdBy = users.uid where tid = ?", 1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IP, &tu.ViewCount, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.Level)
if err == ErrNoRows {
log.Fatal("No rows found!")
return
@ -812,7 +812,7 @@ func BenchmarkQueryPreparedTopicParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
var tu c.TopicUser
getTopicUser, err := qgen.Builder.SimpleLeftJoin("topics", "users", "topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.level", "topics.createdBy = users.uid", "tid = ?", "", "")
getTopicUser, err := qgen.Builder.SimpleLeftJoin("topics", "users", "topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ip, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.level", "topics.createdBy = users.uid", "tid = ?", "", "")
if err != nil {
b.Fatal(err)
}
@ -873,7 +873,7 @@ func BenchmarkQueriesSerial(b *testing.B) {
var tu c.TopicUser
b.Run("topic", func(b *testing.B) {
for i := 0; i < b.N; i++ {
err := db.QueryRow("select topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.level from topics left join users ON topics.createdBy = users.uid where tid = ?", 1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IP, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.Level)
err := db.QueryRow("select topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ip, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.level from topics left join users ON topics.createdBy = users.uid where tid = ?", 1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IP, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.Level)
if err == ErrNoRows {
b.Fatal("No rows found!")
return
@ -885,7 +885,7 @@ func BenchmarkQueriesSerial(b *testing.B) {
})
b.Run("topic_replies", func(b *testing.B) {
for i := 0; i < b.N; i++ {
rows, err := db.Query("select replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.is_super_admin, users.group, users.level, replies.ipaddress from replies left join users ON replies.createdBy = users.uid where tid = ?", 1)
rows, err := db.Query("select replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.is_super_admin, users.group, users.level, replies.ip from replies left join users ON replies.createdBy = users.uid where tid = ?", 1)
if err != nil {
b.Fatal(err)
return
@ -907,7 +907,7 @@ func BenchmarkQueriesSerial(b *testing.B) {
var group int
b.Run("topic_replies_scan", func(b *testing.B) {
for i := 0; i < b.N; i++ {
rows, err := db.Query("select replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.is_super_admin, users.group, users.level, replies.ipaddress from replies left join users ON replies.createdBy = users.uid where tid = ?", 1)
rows, err := db.Query("select replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.is_super_admin, users.group, users.level, replies.ip from replies left join users ON replies.createdBy = users.uid where tid = ?", 1)
if err != nil {
b.Fatal(err)
return

13
main.go
View File

@ -28,6 +28,7 @@ import (
_ "github.com/Azareal/Gosora/extend"
co "github.com/Azareal/Gosora/common/counters"
p "github.com/Azareal/Gosora/common/phrases"
meta "github.com/Azareal/Gosora/common/meta"
"github.com/Azareal/Gosora/query_gen"
"github.com/Azareal/Gosora/routes"
"github.com/fsnotify/fsnotify"
@ -264,6 +265,10 @@ func storeInit() (err error) {
}
// TODO: Let the admin choose other thumbnailers, maybe ones defined in plugins
c.Thumbnailer = c.NewCaireThumbnailer()
c.Recalc, err = c.NewDefaultRecalc(acc)
if err != nil {
return errors.WithStack(err)
}
log.Print("Initialising the view counters")
co.GlobalViewCounter, err = co.NewGlobalViewCounter(acc)
@ -312,7 +317,7 @@ func storeInit() (err error) {
}
log.Print("Initialising the meta store")
c.Meta, err = c.NewDefaultMetaStore(acc)
c.Meta, err = meta.NewDefaultMetaStore(acc)
if err != nil {
return errors.WithStack(err)
}
@ -482,6 +487,12 @@ func main() {
}
}
log.Print("Checking for init tasks")
err = sched()
if err != nil {
c.LogError(err)
}
log.Print("Initialising the task system")
// Thumbnailer goroutine, we only want one image being thumbnailed at a time, otherwise they might wind up consuming all the CPU time and leave no resources left to service the actual requests

View File

@ -917,7 +917,7 @@ func TestReplyStore(t *testing.T) {
testReplyStore(t, 5, 3, "0")
}
func testReplyStore(t *testing.T, newID int, newPostCount int, ip string) {
func testReplyStore(t *testing.T, newID, newPostCount int, ip string) {
replyTest2 := func(reply *c.Reply, err error, rid int, parentID int, createdBy int, content string, ip string) {
expectNilErr(t, err)
expect(t, reply.ID == rid, fmt.Sprintf("RID #%d has the wrong ID. It should be %d not %d", rid, rid, reply.ID))
@ -1028,20 +1028,20 @@ func testProfileReplyStore(t *testing.T, newID int, ip string) {
expectNilErr(t, err)
expect(t, prid == newID, fmt.Sprintf("The first profile reply should have an ID of %d", newID))
profileReply, err := c.Prstore.Get(newID)
pr, err := c.Prstore.Get(newID)
expectNilErr(t, err)
expect(t, profileReply.ID == newID, fmt.Sprintf("The profile reply should have an ID of %d not %d", newID, profileReply.ID))
expect(t, profileReply.ParentID == 1, fmt.Sprintf("The parent ID of the profile reply should be 1 not %d", profileReply.ParentID))
expect(t, profileReply.Content == "Haha", fmt.Sprintf("The profile reply's contents should be 'Haha' not '%s'", profileReply.Content))
expect(t, profileReply.CreatedBy == 1, fmt.Sprintf("The profile reply's creator should be 1 not %d", profileReply.CreatedBy))
expect(t, profileReply.IP == ip, fmt.Sprintf("The profile reply's IP should be '%s' not '%s'", ip, profileReply.IP))
expect(t, pr.ID == newID, fmt.Sprintf("The profile reply should have an ID of %d not %d", newID, pr.ID))
expect(t, pr.ParentID == 1, fmt.Sprintf("The parent ID of the profile reply should be 1 not %d", pr.ParentID))
expect(t, pr.Content == "Haha", fmt.Sprintf("The profile reply's contents should be 'Haha' not '%s'", pr.Content))
expect(t, pr.CreatedBy == 1, fmt.Sprintf("The profile reply's creator should be 1 not %d", pr.CreatedBy))
expect(t, pr.IP == ip, fmt.Sprintf("The profile reply's IP should be '%s' not '%s'", ip, pr.IP))
err = profileReply.Delete()
err = pr.Delete()
expectNilErr(t, err)
_, err = c.Prstore.Get(newID)
expect(t, err != nil, fmt.Sprintf("PRID #%d shouldn't exist after being deleted", newID))
// TODO: Test profileReply.SetBody() and profileReply.Creator()
// TODO: Test pr.SetBody() and pr.Creator()
}
func TestActivityStream(t *testing.T) {

View File

@ -4,7 +4,10 @@ import (
"bufio"
"database/sql"
"strconv"
"strings"
"unicode"
meta "github.com/Azareal/Gosora/common/meta"
qgen "github.com/Azareal/Gosora/query_gen"
)
@ -43,6 +46,7 @@ func init() {
addPatch(26, patch26)
addPatch(27, patch27)
addPatch(28, patch28)
addPatch(29, patch29)
}
func patch0(scanner *bufio.Scanner) (err error) {
@ -750,3 +754,107 @@ func patch27(scanner *bufio.Scanner) error {
func patch28(scanner *bufio.Scanner) error {
return execStmt(qgen.Builder.AddColumn("users", tC{"enable_embeds", "int", 0, false, false, "-1"}, nil))
}
// The word counter might run into problems with some languages where words aren't as obviously demarcated, I would advise turning it off in those cases, or if it becomes annoying in general, really.
func WordCount(input string) (count int) {
input = strings.TrimSpace(input)
if input == "" {
return 0
}
var inSpace bool
for _, value := range input {
if unicode.IsSpace(value) || unicode.IsPunct(value) {
if !inSpace {
inSpace = true
}
} else if inSpace {
count++
inSpace = false
}
}
return count + 1
}
func patch29(scanner *bufio.Scanner) error {
f := func(tbl, idCol string) error {
return acc().Select(tbl).Cols(idCol + ",content").Each(func(rows *sql.Rows) error {
var id int
var content string
err := rows.Scan(&id, &content)
if err != nil {
return err
}
_, err = acc().Update(tbl).Set("words=?").Where(idCol+"=?").Exec(WordCount(content), id)
return err
})
}
err := f("topics", "tid")
if err != nil {
return err
}
err = f("replies", "rid")
if err != nil {
return err
}
meta, err := meta.NewDefaultMetaStore(acc())
if err != nil {
return err
}
err = meta.Set("sched", "recalc")
if err != nil {
return err
}
fixCol := func(tbl string) error {
//err := execStmt(qgen.Builder.RenameColumn(tbl, "ipaddress","ip"))
err := execStmt(qgen.Builder.ChangeColumn(tbl, "ipaddress", tC{"ip", "varchar", 200, false, false, "''"}))
if err != nil {
return err
}
return execStmt(qgen.Builder.SetDefaultColumn(tbl, "ip", "varchar", ""))
}
err = fixCol("topics")
if err != nil {
return err
}
err = fixCol("replies")
if err != nil {
return err
}
err = fixCol("polls_votes")
if err != nil {
return err
}
err = fixCol("users_replies")
if err != nil {
return err
}
err = execStmt(qgen.Builder.SetDefaultColumn("users", "last_ip", "varchar", ""))
if err != nil {
return err
}
err = execStmt(qgen.Builder.SetDefaultColumn("replies", "lastEdit", "int", "0"))
if err != nil {
return err
}
err = execStmt(qgen.Builder.SetDefaultColumn("replies", "lastEditBy", "int", "0"))
if err != nil {
return err
}
err = execStmt(qgen.Builder.SetDefaultColumn("users_replies", "lastEdit", "int", "0"))
if err != nil {
return err
}
err = execStmt(qgen.Builder.SetDefaultColumn("users_replies", "lastEditBy", "int", "0"))
if err != nil {
return err
}
return execStmt(qgen.Builder.AddColumn("activity_stream", tC{"extra", "varchar", 200, false, false, "''"}, nil))
}

View File

@ -18,58 +18,58 @@ type builder struct {
adapter Adapter
}
func (build *builder) Accumulator() *Accumulator {
return &Accumulator{build.conn, build.adapter, nil}
func (b *builder) Accumulator() *Accumulator {
return &Accumulator{b.conn, b.adapter, nil}
}
// TODO: Move this method out of builder?
func (build *builder) Init(adapter string, config map[string]string) error {
err := build.SetAdapter(adapter)
func (b *builder) Init(adapter string, config map[string]string) error {
err := b.SetAdapter(adapter)
if err != nil {
return err
}
conn, err := build.adapter.BuildConn(config)
build.conn = conn
log.Print("err: ", err) // Is the problem here somehow?
conn, err := b.adapter.BuildConn(config)
b.conn = conn
log.Print("err:", err) // Is the problem here somehow?
return err
}
func (build *builder) SetConn(conn *sql.DB) {
build.conn = conn
func (b *builder) SetConn(conn *sql.DB) {
b.conn = conn
}
func (build *builder) GetConn() *sql.DB {
return build.conn
func (b *builder) GetConn() *sql.DB {
return b.conn
}
func (build *builder) SetAdapter(name string) error {
func (b *builder) SetAdapter(name string) error {
adap, err := GetAdapter(name)
if err != nil {
return err
}
build.adapter = adap
b.adapter = adap
return nil
}
func (build *builder) GetAdapter() Adapter {
return build.adapter
func (b *builder) GetAdapter() Adapter {
return b.adapter
}
func (build *builder) DbVersion() (dbVersion string) {
build.conn.QueryRow(build.adapter.DbVersion()).Scan(&dbVersion)
func (b *builder) DbVersion() (dbVersion string) {
b.conn.QueryRow(b.adapter.DbVersion()).Scan(&dbVersion)
return dbVersion
}
func (build *builder) Begin() (*sql.Tx, error) {
return build.conn.Begin()
func (b *builder) Begin() (*sql.Tx, error) {
return b.conn.Begin()
}
func (build *builder) Tx(handler func(*TransactionBuilder) error) error {
tx, err := build.conn.Begin()
func (b *builder) Tx(h func(*TransactionBuilder) error) error {
tx, err := b.conn.Begin()
if err != nil {
return err
}
err = handler(&TransactionBuilder{tx, build.adapter, nil})
err = h(&TransactionBuilder{tx, b.adapter, nil})
if err != nil {
tx.Rollback()
return err
@ -77,83 +77,99 @@ func (build *builder) Tx(handler func(*TransactionBuilder) error) error {
return tx.Commit()
}
func (build *builder) prepare(res string, err error) (*sql.Stmt, error) {
func (b *builder) prepare(res string, err error) (*sql.Stmt, error) {
if err != nil {
return nil, err
}
return build.conn.Prepare(res)
return b.conn.Prepare(res)
}
func (build *builder) SimpleSelect(table string, columns string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.SimpleSelect("", table, columns, where, orderby, limit))
func (b *builder) SimpleSelect(table, columns, where, orderby, limit string) (stmt *sql.Stmt, err error) {
return b.prepare(b.adapter.SimpleSelect("", table, columns, where, orderby, limit))
}
func (build *builder) SimpleCount(table string, where string, limit string) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.SimpleCount("", table, where, limit))
func (b *builder) SimpleCount(table, where, limit string) (stmt *sql.Stmt, err error) {
return b.prepare(b.adapter.SimpleCount("", table, where, limit))
}
func (build *builder) SimpleLeftJoin(table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.SimpleLeftJoin("", table1, table2, columns, joiners, where, orderby, limit))
func (b *builder) SimpleLeftJoin(table1, table2, columns, joiners, where, orderby, limit string) (stmt *sql.Stmt, err error) {
return b.prepare(b.adapter.SimpleLeftJoin("", table1, table2, columns, joiners, where, orderby, limit))
}
func (build *builder) SimpleInnerJoin(table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.SimpleInnerJoin("", table1, table2, columns, joiners, where, orderby, limit))
func (b *builder) SimpleInnerJoin(table1, table2, columns, joiners, where, orderby, limit string) (stmt *sql.Stmt, err error) {
return b.prepare(b.adapter.SimpleInnerJoin("", table1, table2, columns, joiners, where, orderby, limit))
}
func (build *builder) DropTable(table string) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.DropTable("", table))
func (b *builder) DropTable(table string) (stmt *sql.Stmt, err error) {
return b.prepare(b.adapter.DropTable("", table))
}
func (build *builder) CreateTable(table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (stmt *sql.Stmt, err error) {
func (build *builder) CreateTable(table, charset, collation string, columns []DBTableColumn, keys []DBTableKey) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.CreateTable("", table, charset, collation, columns, keys))
}
func (build *builder) AddColumn(table string, column DBTableColumn, key *DBTableKey) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.AddColumn("", table, column, key))
func (b *builder) AddColumn(table string, column DBTableColumn, key *DBTableKey) (stmt *sql.Stmt, err error) {
return b.prepare(b.adapter.AddColumn("", table, column, key))
}
func (build *builder) AddIndex(table string, iname string, colname string) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.AddIndex("", table, iname, colname))
func (b *builder) DropColumn(table, colName string) (stmt *sql.Stmt, err error) {
return b.prepare(b.adapter.DropColumn("", table, colName))
}
func (build *builder) AddKey(table string, column string, key DBTableKey) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.AddKey("", table, column, key))
func (b *builder) RenameColumn(table, oldName, newName string) (stmt *sql.Stmt, err error) {
return b.prepare(b.adapter.RenameColumn("", table, oldName, newName))
}
func (build *builder) AddForeignKey(table string, column string, ftable string, fcolumn string, cascade bool) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.AddForeignKey("", table, column, ftable, fcolumn, cascade))
func (b *builder) ChangeColumn(table, colName string, col DBTableColumn) (stmt *sql.Stmt, err error) {
return b.prepare(b.adapter.ChangeColumn("", table, colName, col))
}
func (build *builder) SimpleInsert(table string, columns string, fields string) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.SimpleInsert("", table, columns, fields))
func (b *builder) SetDefaultColumn(table, colName, colType, defaultStr string) (stmt *sql.Stmt, err error) {
return b.prepare(b.adapter.SetDefaultColumn("", table, colName, colType, defaultStr))
}
func (build *builder) SimpleInsertSelect(ins DBInsert, sel DBSelect) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.SimpleInsertSelect("", ins, sel))
func (b *builder) AddIndex(table, iname, colname string) (stmt *sql.Stmt, err error) {
return b.prepare(b.adapter.AddIndex("", table, iname, colname))
}
func (build *builder) SimpleInsertLeftJoin(ins DBInsert, sel DBJoin) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.SimpleInsertLeftJoin("", ins, sel))
func (b *builder) AddKey(table, column string, key DBTableKey) (stmt *sql.Stmt, err error) {
return b.prepare(b.adapter.AddKey("", table, column, key))
}
func (build *builder) SimpleInsertInnerJoin(ins DBInsert, sel DBJoin) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.SimpleInsertInnerJoin("", ins, sel))
func (b *builder) AddForeignKey(table, column, ftable, fcolumn string, cascade bool) (stmt *sql.Stmt, err error) {
return b.prepare(b.adapter.AddForeignKey("", table, column, ftable, fcolumn, cascade))
}
func (build *builder) SimpleUpdate(table string, set string, where string) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.SimpleUpdate(qUpdate(table, set, where)))
func (b *builder) SimpleInsert(table, columns, fields string) (stmt *sql.Stmt, err error) {
return b.prepare(b.adapter.SimpleInsert("", table, columns, fields))
}
func (build *builder) SimpleDelete(table string, where string) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.SimpleDelete("", table, where))
func (b *builder) SimpleInsertSelect(ins DBInsert, sel DBSelect) (stmt *sql.Stmt, err error) {
return b.prepare(b.adapter.SimpleInsertSelect("", ins, sel))
}
func (b *builder) SimpleInsertLeftJoin(ins DBInsert, sel DBJoin) (stmt *sql.Stmt, err error) {
return b.prepare(b.adapter.SimpleInsertLeftJoin("", ins, sel))
}
func (b *builder) SimpleInsertInnerJoin(ins DBInsert, sel DBJoin) (stmt *sql.Stmt, err error) {
return b.prepare(b.adapter.SimpleInsertInnerJoin("", ins, sel))
}
func (b *builder) SimpleUpdate(table, set, where string) (stmt *sql.Stmt, err error) {
return b.prepare(b.adapter.SimpleUpdate(qUpdate(table, set, where)))
}
func (b *builder) SimpleDelete(table, where string) (stmt *sql.Stmt, err error) {
return b.prepare(b.adapter.SimpleDelete("", table, where))
}
// I don't know why you need this, but here it is x.x
func (build *builder) Purge(table string) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.Purge("", table))
func (b *builder) Purge(table string) (stmt *sql.Stmt, err error) {
return b.prepare(b.adapter.Purge("", table))
}
func (build *builder) prepareTx(tx *sql.Tx, res string, err error) (*sql.Stmt, error) {
func (b *builder) prepareTx(tx *sql.Tx, res string, err error) (*sql.Stmt, error) {
if err != nil {
return nil, err
}
@ -161,63 +177,63 @@ func (build *builder) prepareTx(tx *sql.Tx, res string, err error) (*sql.Stmt, e
}
// These ones support transactions
func (build *builder) SimpleSelectTx(tx *sql.Tx, table string, columns string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleSelect("", table, columns, where, orderby, limit)
return build.prepareTx(tx, res, err)
func (b *builder) SimpleSelectTx(tx *sql.Tx, table, columns, where, orderby, limit string) (stmt *sql.Stmt, err error) {
res, err := b.adapter.SimpleSelect("", table, columns, where, orderby, limit)
return b.prepareTx(tx, res, err)
}
func (build *builder) SimpleCountTx(tx *sql.Tx, table string, where string, limit string) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleCount("", table, where, limit)
return build.prepareTx(tx, res, err)
func (b *builder) SimpleCountTx(tx *sql.Tx, table, where, limit string) (stmt *sql.Stmt, err error) {
res, err := b.adapter.SimpleCount("", table, where, limit)
return b.prepareTx(tx, res, err)
}
func (build *builder) SimpleLeftJoinTx(tx *sql.Tx, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleLeftJoin("", table1, table2, columns, joiners, where, orderby, limit)
return build.prepareTx(tx, res, err)
func (b *builder) SimpleLeftJoinTx(tx *sql.Tx, table1, table2, columns, joiners, where, orderby, limit string) (stmt *sql.Stmt, err error) {
res, err := b.adapter.SimpleLeftJoin("", table1, table2, columns, joiners, where, orderby, limit)
return b.prepareTx(tx, res, err)
}
func (build *builder) SimpleInnerJoinTx(tx *sql.Tx, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleInnerJoin("", table1, table2, columns, joiners, where, orderby, limit)
return build.prepareTx(tx, res, err)
func (b *builder) SimpleInnerJoinTx(tx *sql.Tx, table1, table2, columns, joiners, where, orderby, limit string) (stmt *sql.Stmt, err error) {
res, err := b.adapter.SimpleInnerJoin("", table1, table2, columns, joiners, where, orderby, limit)
return b.prepareTx(tx, res, err)
}
func (build *builder) CreateTableTx(tx *sql.Tx, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (stmt *sql.Stmt, err error) {
res, err := build.adapter.CreateTable("", table, charset, collation, columns, keys)
return build.prepareTx(tx, res, err)
func (b *builder) CreateTableTx(tx *sql.Tx, table, charset, collation string, columns []DBTableColumn, keys []DBTableKey) (stmt *sql.Stmt, err error) {
res, err := b.adapter.CreateTable("", table, charset, collation, columns, keys)
return b.prepareTx(tx, res, err)
}
func (build *builder) SimpleInsertTx(tx *sql.Tx, table string, columns string, fields string) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleInsert("", table, columns, fields)
return build.prepareTx(tx, res, err)
func (b *builder) SimpleInsertTx(tx *sql.Tx, table, columns, fields string) (stmt *sql.Stmt, err error) {
res, err := b.adapter.SimpleInsert("", table, columns, fields)
return b.prepareTx(tx, res, err)
}
func (build *builder) SimpleInsertSelectTx(tx *sql.Tx, ins DBInsert, sel DBSelect) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleInsertSelect("", ins, sel)
return build.prepareTx(tx, res, err)
func (b *builder) SimpleInsertSelectTx(tx *sql.Tx, ins DBInsert, sel DBSelect) (stmt *sql.Stmt, err error) {
res, err := b.adapter.SimpleInsertSelect("", ins, sel)
return b.prepareTx(tx, res, err)
}
func (build *builder) SimpleInsertLeftJoinTx(tx *sql.Tx, ins DBInsert, sel DBJoin) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleInsertLeftJoin("", ins, sel)
return build.prepareTx(tx, res, err)
func (b *builder) SimpleInsertLeftJoinTx(tx *sql.Tx, ins DBInsert, sel DBJoin) (stmt *sql.Stmt, err error) {
res, err := b.adapter.SimpleInsertLeftJoin("", ins, sel)
return b.prepareTx(tx, res, err)
}
func (build *builder) SimpleInsertInnerJoinTx(tx *sql.Tx, ins DBInsert, sel DBJoin) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleInsertInnerJoin("", ins, sel)
return build.prepareTx(tx, res, err)
func (b *builder) SimpleInsertInnerJoinTx(tx *sql.Tx, ins DBInsert, sel DBJoin) (stmt *sql.Stmt, err error) {
res, err := b.adapter.SimpleInsertInnerJoin("", ins, sel)
return b.prepareTx(tx, res, err)
}
func (build *builder) SimpleUpdateTx(tx *sql.Tx, table string, set string, where string) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleUpdate(qUpdate(table, set, where))
return build.prepareTx(tx, res, err)
func (b *builder) SimpleUpdateTx(tx *sql.Tx, table, set, where string) (stmt *sql.Stmt, err error) {
res, err := b.adapter.SimpleUpdate(qUpdate(table, set, where))
return b.prepareTx(tx, res, err)
}
func (build *builder) SimpleDeleteTx(tx *sql.Tx, table string, where string) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleDelete("", table, where)
return build.prepareTx(tx, res, err)
func (b *builder) SimpleDeleteTx(tx *sql.Tx, table, where string) (stmt *sql.Stmt, err error) {
res, err := b.adapter.SimpleDelete("", table, where)
return b.prepareTx(tx, res, err)
}
// I don't know why you need this, but here it is x.x
func (build *builder) PurgeTx(tx *sql.Tx, table string) (stmt *sql.Stmt, err error) {
res, err := build.adapter.Purge("", table)
return build.prepareTx(tx, res, err)
func (b *builder) PurgeTx(tx *sql.Tx, table string) (stmt *sql.Stmt, err error) {
res, err := b.adapter.Purge("", table)
return b.prepareTx(tx, res, err)
}

View File

@ -56,7 +56,7 @@ func (a *MssqlAdapter) DropTable(name, table string) (string, error) {
// TODO: Add support for foreign keys?
// TODO: Convert any remaining stringy types to nvarchar
// We may need to change the CreateTable API to better suit Mssql and the other database drivers which are coming up
func (a *MssqlAdapter) CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) {
func (a *MssqlAdapter) CreateTable(name, table, charset, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) {
if table == "" {
return "", errors.New("You need a name for this table")
}
@ -137,7 +137,7 @@ func (a *MssqlAdapter) parseColumn(column DBTableColumn) (col DBTableColumn, siz
// TODO: Test this, not sure if some things work
// TODO: Add support for keys
func (a *MssqlAdapter) AddColumn(name string, table string, column DBTableColumn, key *DBTableKey) (string, error) {
func (a *MssqlAdapter) AddColumn(name, table string, column DBTableColumn, key *DBTableKey) (string, error) {
if table == "" {
return "", errors.New("You need a name for this table")
}
@ -148,6 +148,29 @@ func (a *MssqlAdapter) AddColumn(name string, table string, column DBTableColumn
return q, nil
}
// TODO: Implement this
func (a *MssqlAdapter) DropColumn(name, table, colName string) (string, error) {
return "", errors.New("not implemented")
}
// TODO: Implement this
func (a *MssqlAdapter) RenameColumn(name, table, oldName, newName string) (string, error) {
return "", errors.New("not implemented")
}
// TODO: Implement this
func (a *MssqlAdapter) ChangeColumn(name, table, colName string, col DBTableColumn) (string, error) {
return "", errors.New("not implemented")
}
// TODO: Implement this
func (a *MssqlAdapter) SetDefaultColumn(name, table, colName, colType, defaultStr string) (string, error) {
if colType == "text" {
return "", errors.New("text fields cannot have default values")
}
return "", errors.New("not implemented")
}
// TODO: Implement this
// TODO: Test to make sure everything works here
func (a *MssqlAdapter) AddIndex(name, table, iname, colname string) (string, error) {

View File

@ -92,7 +92,7 @@ func (a *MysqlAdapter) DropTable(name, table string) (string, error) {
return q, nil
}
func (a *MysqlAdapter) CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) {
func (a *MysqlAdapter) CreateTable(name, table, charset, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) {
if table == "" {
return "", errors.New("You need a name for this table")
}
@ -143,53 +143,95 @@ func (a *MysqlAdapter) CreateTable(name string, table string, charset string, co
return q, nil
}
func (a *MysqlAdapter) parseColumn(column DBTableColumn) (col DBTableColumn, size string, end string) {
// Make it easier to support Cassandra in the future
if column.Type == "createdAt" {
column.Type = "datetime"
// MySQL doesn't support this x.x
/*if column.Default == "" {
column.Default = "UTC_TIMESTAMP()"
}*/
} else if column.Type == "json" {
column.Type = "text"
func (a *MysqlAdapter) DropColumn(name, table, colName string) (string, error) {
q := "ALTER TABLE `" + table + "` DROP COLUMN `" + colName + "`;"
a.pushStatement(name, "drop-column", q)
return q, nil
}
// ! Currently broken in MariaDB. Planned.
func (a *MysqlAdapter) RenameColumn(name, table, oldName, newName string) (string, error) {
q := "ALTER TABLE `" + table + "` RENAME COLUMN `" + oldName + "` TO `" + newName + "`;"
a.pushStatement(name, "rename-column", q)
return q, nil
}
func (a *MysqlAdapter) ChangeColumn(name, table, colName string, col DBTableColumn) (string, error) {
col.Default = ""
col, size, end := a.parseColumn(col)
q := "ALTER TABLE `" + table + "` CHANGE COLUMN `" + colName + "` `" + col.Name + "` " + col.Type + size + end
a.pushStatement(name, "change-column", q)
return q, nil
}
func (a *MysqlAdapter) SetDefaultColumn(name, table, colName, colType, defaultStr string) (string, error) {
if colType == "text" {
return "", errors.New("text fields cannot have default values")
}
if column.Size > 0 {
size = "(" + strconv.Itoa(column.Size) + ")"
if defaultStr == "" {
defaultStr = "''"
}
// TODO: Exclude the other variants of text like mediumtext and longtext too
expr := ""
/*if colType == "datetime" && defaultStr[len(defaultStr)-1] == ')' {
end += defaultStr
} else */if a.stringyType(colType) && defaultStr != "''" {
expr += "'" + defaultStr + "'"
} else {
expr += defaultStr
}
q := "ALTER TABLE `" + table + "` ALTER COLUMN `" + colName + "` SET DEFAULT " + expr + ";"
a.pushStatement(name, "set-default-column", q)
return q, nil
}
func (a *MysqlAdapter) parseColumn(col DBTableColumn) (ocol DBTableColumn, size, end string) {
// Make it easier to support Cassandra in the future
if col.Type == "createdAt" {
col.Type = "datetime"
// MySQL doesn't support this x.x
/*if col.Default == "" {
col.Default = "UTC_TIMESTAMP()"
}*/
} else if col.Type == "json" {
col.Type = "text"
}
if col.Size > 0 {
size = "(" + strconv.Itoa(col.Size) + ")"
}
// TODO: Exclude the other variants of text like mediumtext and longtext too
if column.Default != "" && column.Type != "text" {
if col.Default != "" && col.Type != "text" {
end = " DEFAULT "
/*if column.Type == "datetime" && column.Default[len(column.Default)-1] == ')' {
/*if col.Type == "datetime" && col.Default[len(col.Default)-1] == ')' {
end += column.Default
} else */if a.stringyType(column.Type) && column.Default != "''" {
end += "'" + column.Default + "'"
} else */if a.stringyType(col.Type) && col.Default != "''" {
end += "'" + col.Default + "'"
} else {
end += column.Default
end += col.Default
}
}
if column.Null {
if col.Null {
end += " null"
} else {
end += " not null"
}
if column.AutoIncrement {
if col.AutoIncrement {
end += " AUTO_INCREMENT"
}
return column, size, end
return col, size, end
}
// TODO: Support AFTER column
// TODO: Test to make sure everything works here
func (a *MysqlAdapter) AddColumn(name string, table string, column DBTableColumn, key *DBTableKey) (string, error) {
func (a *MysqlAdapter) AddColumn(name, table string, col DBTableColumn, key *DBTableKey) (string, error) {
if table == "" {
return "", errors.New("You need a name for this table")
}
column, size, end := a.parseColumn(column)
q := "ALTER TABLE `" + table + "` ADD COLUMN " + "`" + column.Name + "` " + column.Type + size + end
col, size, end := a.parseColumn(col)
q := "ALTER TABLE `" + table + "` ADD COLUMN " + "`" + col.Name + "` " + col.Type + size + end
if key != nil {
q += " " + key.Type
@ -225,7 +267,7 @@ func (a *MysqlAdapter) AddIndex(name, table, iname, colname string) (string, err
// TODO: Test to make sure everything works here
// Only supports FULLTEXT right now
func (a *MysqlAdapter) AddKey(name string, table string, column string, key DBTableKey) (string, error) {
func (a *MysqlAdapter) AddKey(name, table, column string, key DBTableKey) (string, error) {
if table == "" {
return "", errors.New("You need a name for this table")
}
@ -241,7 +283,7 @@ func (a *MysqlAdapter) AddKey(name string, table string, column string, key DBTa
return q, nil
}
func (a *MysqlAdapter) AddForeignKey(name string, table string, column string, ftable string, fcolumn string, cascade bool) (out string, e error) {
func (a *MysqlAdapter) AddForeignKey(name, table, column, ftable, fcolumn string, cascade bool) (out string, e error) {
c := func(str string, val bool) {
if e != nil || !val {
return
@ -338,7 +380,7 @@ func (a *MysqlAdapter) SimpleReplace(name, table, columns, fields string) (strin
for _, field := range processFields(fields) {
q += field.Name + ","
}
q = q[0 : len(q)-1] + ")"
q = q[0:len(q)-1] + ")"
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
a.pushStatement(name, "replace", q)
@ -362,8 +404,7 @@ func (a *MysqlAdapter) SimpleUpsert(name, table, columns, fields, where string)
q := "INSERT INTO `" + table + "`("
parsedFields := processFields(fields)
var insertColumns string
var insertValues string
var insertColumns, insertValues string
setBit := ") ON DUPLICATE KEY UPDATE "
for columnID, col := range processColumns(columns) {
@ -667,7 +708,7 @@ func (a *MysqlAdapter) complexSelect(preBuilder *selectPrebuilder, sb *strings.B
return nil
}
func (a *MysqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
func (a *MysqlAdapter) SimpleLeftJoin(name, table1, table2, columns, joiners, where, orderby, limit string) (string, error) {
if table1 == "" {
return "", errors.New("You need a name for the left table")
}
@ -704,7 +745,7 @@ func (a *MysqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string,
return q, nil
}
func (a *MysqlAdapter) SimpleInnerJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
func (a *MysqlAdapter) SimpleInnerJoin(name, table1, table2, columns, joiners, where, orderby, limit string) (string, error) {
if table1 == "" {
return "", errors.New("You need a name for the left table")
}

View File

@ -42,7 +42,7 @@ func (a *PgsqlAdapter) DbVersion() string {
return "SELECT version()"
}
func (a *PgsqlAdapter) DropTable(name string, table string) (string, error) {
func (a *PgsqlAdapter) DropTable(name, table string) (string, error) {
if table == "" {
return "", errors.New("You need a name for this table")
}
@ -53,7 +53,7 @@ func (a *PgsqlAdapter) DropTable(name string, table string) (string, error) {
// TODO: Implement this
// We may need to change the CreateTable API to better suit PGSQL and the other database drivers which are coming up
func (a *PgsqlAdapter) CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) {
func (a *PgsqlAdapter) CreateTable(name, table, charset, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) {
if table == "" {
return "", errors.New("You need a name for this table")
}
@ -112,16 +112,39 @@ func (a *PgsqlAdapter) CreateTable(name string, table string, charset string, co
}
// TODO: Implement this
func (a *PgsqlAdapter) AddColumn(name string, table string, column DBTableColumn, key *DBTableKey) (string, error) {
func (a *PgsqlAdapter) AddColumn(name, table string, column DBTableColumn, key *DBTableKey) (string, error) {
if table == "" {
return "", errors.New("You need a name for this table")
}
return "", nil
}
// TODO: Implement this
func (a *PgsqlAdapter) DropColumn(name, table, colName string) (string, error) {
return "", errors.New("not implemented")
}
// TODO: Implement this
func (a *PgsqlAdapter) RenameColumn(name, table, oldName, newName string) (string, error) {
return "", errors.New("not implemented")
}
// TODO: Implement this
func (a *PgsqlAdapter) ChangeColumn(name, table, colName string, col DBTableColumn) (string, error) {
return "", errors.New("not implemented")
}
// TODO: Implement this
func (a *PgsqlAdapter) SetDefaultColumn(name, table, colName, colType, defaultStr string) (string, error) {
if colType == "text" {
return "", errors.New("text fields cannot have default values")
}
return "", errors.New("not implemented")
}
// TODO: Implement this
// TODO: Test to make sure everything works here
func (a *PgsqlAdapter) AddIndex(name string, table string, iname string, colname string) (string, error) {
func (a *PgsqlAdapter) AddIndex(name, table, iname, colname string) (string, error) {
if table == "" {
return "", errors.New("You need a name for this table")
}
@ -136,7 +159,7 @@ func (a *PgsqlAdapter) AddIndex(name string, table string, iname string, colname
// TODO: Implement this
// TODO: Test to make sure everything works here
func (a *PgsqlAdapter) AddKey(name string, table string, column string, key DBTableKey) (string, error) {
func (a *PgsqlAdapter) AddKey(name, table, column string, key DBTableKey) (string, error) {
if table == "" {
return "", errors.New("You need a name for this table")
}
@ -148,7 +171,7 @@ func (a *PgsqlAdapter) AddKey(name string, table string, column string, key DBTa
// TODO: Implement this
// TODO: Test to make sure everything works here
func (a *PgsqlAdapter) AddForeignKey(name string, table string, column string, ftable string, fcolumn string, cascade bool) (out string, e error) {
func (a *PgsqlAdapter) AddForeignKey(name, table, column, ftable, fcolumn string, cascade bool) (out string, e error) {
var c = func(str string, val bool) {
if e != nil || !val {
return
@ -167,7 +190,7 @@ func (a *PgsqlAdapter) AddForeignKey(name string, table string, column string, f
// TODO: Test this
// ! We need to get the last ID out of this somehow, maybe add returning to every query? Might require some sort of wrapper over the sql statements
func (a *PgsqlAdapter) SimpleInsert(name string, table string, columns string, fields string) (string, error) {
func (a *PgsqlAdapter) SimpleInsert(name, table, columns, fields string) (string, error) {
if table == "" {
return "", errors.New("You need a name for this table")
}
@ -225,7 +248,7 @@ func (a *PgsqlAdapter) SimpleReplace(name, table, columns, fields string) (strin
}
// TODO: Implement this
func (a *PgsqlAdapter) SimpleUpsert(name string, table string, columns string, fields string, where string) (string, error) {
func (a *PgsqlAdapter) SimpleUpsert(name, table, columns, fields, where string) (string, error) {
if table == "" {
return "", errors.New("You need a name for this table")
}
@ -307,7 +330,7 @@ func (a *PgsqlAdapter) SimpleUpdateSelect(up *updatePrebuilder) (string, error)
}
// TODO: Implement this
func (a *PgsqlAdapter) SimpleDelete(name string, table string, where string) (string, error) {
func (a *PgsqlAdapter) SimpleDelete(name, table, where string) (string, error) {
if table == "" {
return "", errors.New("You need a name for this table")
}
@ -330,7 +353,7 @@ func (a *PgsqlAdapter) ComplexDelete(b *deletePrebuilder) (string, error) {
// TODO: Implement this
// We don't want to accidentally wipe tables, so we'll have a separate method for purging tables instead
func (a *PgsqlAdapter) Purge(name string, table string) (string, error) {
func (a *PgsqlAdapter) Purge(name, table string) (string, error) {
if table == "" {
return "", errors.New("You need a name for this table")
}
@ -338,7 +361,7 @@ func (a *PgsqlAdapter) Purge(name string, table string) (string, error) {
}
// TODO: Implement this
func (a *PgsqlAdapter) SimpleSelect(name string, table string, columns string, where string, orderby string, limit string) (string, error) {
func (a *PgsqlAdapter) SimpleSelect(name, table, columns, where, orderby, limit string) (string, error) {
if table == "" {
return "", errors.New("You need a name for this table")
}
@ -360,7 +383,7 @@ func (a *PgsqlAdapter) ComplexSelect(prebuilder *selectPrebuilder) (string, erro
}
// TODO: Implement this
func (a *PgsqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
func (a *PgsqlAdapter) SimpleLeftJoin(name, table1, table2, columns, joiners, where, orderby, limit string) (string, error) {
if table1 == "" {
return "", errors.New("You need a name for the left table")
}
@ -377,7 +400,7 @@ func (a *PgsqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string,
}
// TODO: Implement this
func (a *PgsqlAdapter) SimpleInnerJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
func (a *PgsqlAdapter) SimpleInnerJoin(name, table1, table2, columns, joiners, where, orderby, limit string) (string, error) {
if table1 == "" {
return "", errors.New("You need a name for the left table")
}
@ -409,7 +432,7 @@ func (a *PgsqlAdapter) SimpleInsertInnerJoin(name string, ins DBInsert, sel DBJo
}
// TODO: Implement this
func (a *PgsqlAdapter) SimpleCount(name string, table string, where string, limit string) (string, error) {
func (a *PgsqlAdapter) SimpleCount(name, table, where, limit string) (string, error) {
if table == "" {
return "", errors.New("You need a name for this table")
}
@ -471,7 +494,7 @@ func _gen_pgsql() (err error) {
}
// Internal methods, not exposed in the interface
func (a *PgsqlAdapter) pushStatement(name string, stype string, q string) {
func (a *PgsqlAdapter) pushStatement(name, stype, q string) {
if name == "" {
return
}

View File

@ -130,22 +130,26 @@ type Adapter interface {
BuildConn(config map[string]string) (*sql.DB, error)
DbVersion() string
DropTable(name string, table string) (string, error)
CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error)
DropTable(name, table string) (string, error)
CreateTable(name, table, charset, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error)
// TODO: Some way to add indices and keys
// TODO: Test this
AddColumn(name string, table string, column DBTableColumn, key *DBTableKey) (string, error)
AddIndex(name string, table string, iname string, colname string) (string, error)
AddKey(name string, table string, column string, key DBTableKey) (string, error)
AddForeignKey(name string, table string, column string, ftable string, fcolumn string, cascade bool) (out string, e error)
SimpleInsert(name string, table string, columns string, fields string) (string, error)
AddColumn(name, table string, column DBTableColumn, key *DBTableKey) (string, error)
DropColumn(name, table, colname string) (string, error)
RenameColumn(name, table, oldName, newName string) (string, error)
ChangeColumn(name, table, colName string, col DBTableColumn) (string, error)
SetDefaultColumn(name, table, colName, colType, defaultStr string) (string, error)
AddIndex(name, table, iname, colname string) (string, error)
AddKey(name, table, column string, key DBTableKey) (string, error)
AddForeignKey(name, table, column, ftable, fcolumn string, cascade bool) (out string, e error)
SimpleInsert(name, table, columns, fields string) (string, error)
SimpleUpdate(b *updatePrebuilder) (string, error)
SimpleUpdateSelect(b *updatePrebuilder) (string, error) // ! Experimental
SimpleDelete(name string, table string, where string) (string, error)
Purge(name string, table string) (string, error)
SimpleSelect(name string, table string, columns string, where string, orderby string, limit string) (string, error)
SimpleDelete(name, table, where string) (string, error)
Purge(name, table string) (string, error)
SimpleSelect(name, table, columns, where, orderby, limit string) (string, error)
ComplexDelete(b *deletePrebuilder) (string, error)
SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error)
SimpleLeftJoin(name, table1, table2, columns, joiners, where, orderby, limit string) (string, error)
SimpleInnerJoin(string, string, string, string, string, string, string, string) (string, error)
SimpleInsertSelect(string, DBInsert, DBSelect) (string, error)
SimpleInsertLeftJoin(string, DBInsert, DBJoin) (string, error)
@ -192,9 +196,9 @@ func PrepareMySQLUpsertCallback(db *sql.DB, query string) (*MySQLUpsertCallback,
type LitStr string
// TODO: Test this
func InterfaceMapToInsertStrings(data map[string]interface{}, order string) (cols string, values string) {
var done = make(map[string]bool)
var addValue = func(value interface{}) {
func InterfaceMapToInsertStrings(data map[string]interface{}, order string) (cols, values string) {
done := make(map[string]bool)
addValue := func(value interface{}) {
switch value := value.(type) {
case string:
values += "'" + strings.Replace(value, "'", "\\'", -1) + "',"

View File

@ -127,6 +127,7 @@ func topicRoutes() *RouteGroup {
Action("routes.UnlockTopicSubmit", "/topic/unlock/submit/", "extraData"),
Action("routes.MoveTopicSubmit", "/topic/move/submit/", "extraData"),
Action("routes.LikeTopicSubmit", "/topic/like/submit/", "extraData"),
Action("routes.UnlikeTopicSubmit", "/topic/unlike/submit/", "extraData"),
UploadAction("routes.AddAttachToTopicSubmit", "/topic/attach/add/submit/", "extraData").MaxSizeVar("int(c.Config.MaxRequestSize)"),
Action("routes.RemoveAttachFromTopicSubmit", "/topic/attach/remove/submit/", "extraData"),
)

View File

@ -66,7 +66,7 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError
}
// Don't want to throw an internal error due to a socket closing
if c.EnableWebsockets && count > 0 {
_ = c.WsHub.PushMessage(user.ID, `{"event":"dismiss-alert","id":`+strconv.Itoa(id)+`}`)
c.DismissAlert(user.ID, id)
}
w.Write(successJSONBytes)
// TODO: Split this into it's own function

View File

@ -39,13 +39,13 @@ func ProfileReplyCreateSubmit(w http.ResponseWriter, r *http.Request, user c.Use
return c.LocalError("You can't make a blank post", w, r, user)
}
// TODO: Fully parse the post and store it in the parsed column
_, err = c.Prstore.Create(profileOwner.ID, content, user.ID, user.GetIP())
prid, err := c.Prstore.Create(profileOwner.ID, content, user.ID, user.GetIP())
if err != nil {
return c.InternalError(err, w, r)
}
// ! Be careful about leaking per-route permission state with &user
alert := c.Alert{ActorID: user.ID, TargetUserID: profileOwner.ID, Event: "reply", ElementType: "user", ElementID: profileOwner.ID, Actor: &user}
alert := c.Alert{ActorID: user.ID, TargetUserID: profileOwner.ID, Event: "reply", ElementType: "user", ElementID: profileOwner.ID, Actor: &user, Extra: strconv.Itoa(prid)}
err = c.AddActivityAndNotifyTarget(alert)
if err != nil {
return c.InternalError(err, w, r)

View File

@ -136,7 +136,7 @@ func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Ro
return c.InternalErrorJSQ(err, w, r, js)
}
c.AddActivityAndNotifyAll(user.ID, topic.CreatedBy, "reply", "topic", tid)
c.AddActivityAndNotifyAll(c.Alert{ActorID: user.ID, TargetUserID: topic.CreatedBy, Event: "reply", ElementType: "topic", ElementID: tid, Extra: strconv.Itoa(rid)})
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}

View File

@ -346,10 +346,10 @@ func CreateTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Ro
return c.NoPermissions(w, r, user)
}
tname := c.SanitiseSingleLine(r.PostFormValue("name"))
name := c.SanitiseSingleLine(r.PostFormValue("name"))
content := c.PreparseMessage(r.PostFormValue("content"))
// TODO: Fully parse the post and store it in the parsed column
tid, err := c.Topics.Create(fid, tname, content, user.ID, user.GetIP())
tid, err := c.Topics.Create(fid, name, content, user.ID, user.GetIP())
if err != nil {
switch err {
case c.ErrNoRows:
@ -959,3 +959,55 @@ func LikeTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid s
}
return nil
}
func UnlikeTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid string) c.RouteError {
js := r.PostFormValue("js") == "1"
tid, err := strconv.Atoi(stid)
if err != nil {
return c.PreErrorJSQ(phrases.GetErrorPhrase("id_must_be_integer"), w, r, js)
}
topic, err := c.Topics.Get(tid)
if err == sql.ErrNoRows {
return c.PreErrorJSQ("The requested topic doesn't exist.", w, r, js)
} else if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
// TODO: Add hooks to make use of headerLite
lite, ferr := c.SimpleForumUserCheck(w, r, &user, topic.ParentID)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic || !user.Perms.LikeItem {
return c.NoPermissionsJSQ(w, r, user, js)
}
_, err = c.Users.Get(topic.CreatedBy)
if err != nil && err == sql.ErrNoRows {
return c.LocalErrorJSQ("The target user doesn't exist", w, r, user, js)
} else if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
err = topic.Unlike(user.ID)
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
// TODO: Push dismiss-event alerts to the users.
err = c.Activity.DeleteByParams("like", topic.ID, "topic")
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
skip, rerr := lite.Hooks.VhookSkippable("action_end_unlike_topic", topic.ID, &user)
if skip || rerr != nil {
return rerr
}
if !js {
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
} else {
_, _ = w.Write(successJSONBytes)
}
return nil
}

View File

@ -27,8 +27,8 @@ INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (3,2,'{"View
INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (4,2,'{"ViewTopic":true}');
INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (5,2,'{"ViewTopic":true}');
INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (6,2,'{"ViewTopic":true}');
INSERT INTO [topics] ([title],[content],[parsed_content],[createdAt],[lastReplyAt],[lastReplyBy],[createdBy],[parentID],[ipaddress]) VALUES ('Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',GETUTCDATE(),GETUTCDATE(),1,1,2,'::1');
INSERT INTO [replies] ([tid],[content],[parsed_content],[createdAt],[createdBy],[lastUpdated],[lastEdit],[lastEditBy],[ipaddress]) VALUES (1,'A reply!','A reply!',GETUTCDATE(),1,GETUTCDATE(),0,0,'::1');
INSERT INTO [topics] ([title],[content],[parsed_content],[createdAt],[lastReplyAt],[lastReplyBy],[createdBy],[parentID],[ip]) VALUES ('Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',GETUTCDATE(),GETUTCDATE(),1,1,2,'::1');
INSERT INTO [replies] ([tid],[content],[parsed_content],[createdAt],[createdBy],[lastUpdated],[lastEdit],[lastEditBy],[ip]) VALUES (1,'A reply!','A reply!',GETUTCDATE(),1,GETUTCDATE(),0,0,'::1');
INSERT INTO [menus] () VALUES ();
INSERT INTO [menu_items] ([mid],[name],[htmlID],[position],[path],[aria],[tooltip],[order]) VALUES (1,'{lang.menu_forums}','menu_forums','left','/forums/','{lang.menu_forums_aria}','{lang.menu_forums_tooltip}',0);
INSERT INTO [menu_items] ([mid],[name],[htmlID],[cssClass],[position],[path],[aria],[tooltip],[order]) VALUES (1,'{lang.menu_topics}','menu_topics','menu_topics','left','/topics/','{lang.menu_topics_aria}','{lang.menu_topics_tooltip}',1);

View File

@ -6,5 +6,6 @@ CREATE TABLE [activity_stream] (
[elementType] nvarchar (50) not null,
[elementID] int not null,
[createdAt] datetime not null,
[extra] nvarchar (200) DEFAULT '' not null,
primary key([asid])
);

View File

@ -3,5 +3,5 @@ CREATE TABLE [polls_votes] (
[uid] int not null,
[option] int DEFAULT 0 not null,
[castAt] datetime not null,
[ipaddress] nvarchar (200) DEFAULT '0.0.0.0.0' not null
[ip] nvarchar (200) DEFAULT '' not null
);

View File

@ -8,7 +8,7 @@ CREATE TABLE [replies] (
[lastEdit] int DEFAULT 0 not null,
[lastEditBy] int DEFAULT 0 not null,
[lastUpdated] datetime not null,
[ipaddress] nvarchar (200) DEFAULT '0.0.0.0.0' not null,
[ip] nvarchar (200) DEFAULT '' not null,
[likeCount] int DEFAULT 0 not null,
[attachCount] int DEFAULT 0 not null,
[words] int DEFAULT 1 not null,

View File

@ -11,7 +11,7 @@ CREATE TABLE [topics] (
[is_closed] bit DEFAULT 0 not null,
[sticky] bit DEFAULT 0 not null,
[parentID] int DEFAULT 2 not null,
[ipaddress] nvarchar (200) DEFAULT '0.0.0.0.0' not null,
[ip] nvarchar (200) DEFAULT '' not null,
[postCount] int DEFAULT 1 not null,
[likeCount] int DEFAULT 0 not null,
[attachCount] int DEFAULT 0 not null,

View File

@ -7,6 +7,6 @@ CREATE TABLE [users_replies] (
[createdBy] int not null,
[lastEdit] int DEFAULT 0 not null,
[lastEditBy] int DEFAULT 0 not null,
[ipaddress] nvarchar (200) DEFAULT '0.0.0.0.0' not null,
[ip] nvarchar (200) DEFAULT '' not null,
primary key([rid])
);

View File

@ -35,8 +35,8 @@ INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (3,2,'{"ViewT
INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (4,2,'{"ViewTopic":true}');
INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (5,2,'{"ViewTopic":true}');
INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (6,2,'{"ViewTopic":true}');
INSERT INTO `topics`(`title`,`content`,`parsed_content`,`createdAt`,`lastReplyAt`,`lastReplyBy`,`createdBy`,`parentID`,`ipaddress`) VALUES ('Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2,'::1');
INSERT INTO `replies`(`tid`,`content`,`parsed_content`,`createdAt`,`createdBy`,`lastUpdated`,`lastEdit`,`lastEditBy`,`ipaddress`) VALUES (1,'A reply!','A reply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0,'::1');
INSERT INTO `topics`(`title`,`content`,`parsed_content`,`createdAt`,`lastReplyAt`,`lastReplyBy`,`createdBy`,`parentID`,`ip`) VALUES ('Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2,'::1');
INSERT INTO `replies`(`tid`,`content`,`parsed_content`,`createdAt`,`createdBy`,`lastUpdated`,`lastEdit`,`lastEditBy`,`ip`) VALUES (1,'A reply!','A reply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0,'::1');
INSERT INTO `menus`() VALUES ();
INSERT INTO `menu_items`(`mid`,`name`,`htmlID`,`position`,`path`,`aria`,`tooltip`,`order`) VALUES (1,'{lang.menu_forums}','menu_forums','left','/forums/','{lang.menu_forums_aria}','{lang.menu_forums_tooltip}',0);
INSERT INTO `menu_items`(`mid`,`name`,`htmlID`,`cssClass`,`position`,`path`,`aria`,`tooltip`,`order`) VALUES (1,'{lang.menu_topics}','menu_topics','menu_topics','left','/topics/','{lang.menu_topics_aria}','{lang.menu_topics_tooltip}',1);

View File

@ -6,5 +6,6 @@ CREATE TABLE `activity_stream` (
`elementType` varchar(50) not null,
`elementID` int not null,
`createdAt` datetime not null,
`extra` varchar(200) DEFAULT '' not null,
primary key(`asid`)
);

View File

@ -3,5 +3,5 @@ CREATE TABLE `polls_votes` (
`uid` int not null,
`option` int DEFAULT 0 not null,
`castAt` datetime not null,
`ipaddress` varchar(200) DEFAULT '0.0.0.0.0' not null
`ip` varchar(200) DEFAULT '' not null
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;

View File

@ -8,7 +8,7 @@ CREATE TABLE `replies` (
`lastEdit` int DEFAULT 0 not null,
`lastEditBy` int DEFAULT 0 not null,
`lastUpdated` datetime not null,
`ipaddress` varchar(200) DEFAULT '0.0.0.0.0' not null,
`ip` varchar(200) DEFAULT '' not null,
`likeCount` int DEFAULT 0 not null,
`attachCount` int DEFAULT 0 not null,
`words` int DEFAULT 1 not null,

View File

@ -11,7 +11,7 @@ CREATE TABLE `topics` (
`is_closed` boolean DEFAULT 0 not null,
`sticky` boolean DEFAULT 0 not null,
`parentID` int DEFAULT 2 not null,
`ipaddress` varchar(200) DEFAULT '0.0.0.0.0' not null,
`ip` varchar(200) DEFAULT '' not null,
`postCount` int DEFAULT 1 not null,
`likeCount` int DEFAULT 0 not null,
`attachCount` int DEFAULT 0 not null,

View File

@ -7,6 +7,6 @@ CREATE TABLE `users_replies` (
`createdBy` int not null,
`lastEdit` int DEFAULT 0 not null,
`lastEditBy` int DEFAULT 0 not null,
`ipaddress` varchar(200) DEFAULT '0.0.0.0.0' not null,
`ip` varchar(200) DEFAULT '' not null,
primary key(`rid`)
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;

View File

@ -27,8 +27,8 @@ INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (3,2,'{"ViewT
INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (4,2,'{"ViewTopic":true}');
INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (5,2,'{"ViewTopic":true}');
INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (6,2,'{"ViewTopic":true}');
INSERT INTO "topics"("title","content","parsed_content","createdAt","lastReplyAt","lastReplyBy","createdBy","parentID","ipaddress") VALUES ('Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2,'::1');
INSERT INTO "replies"("tid","content","parsed_content","createdAt","createdBy","lastUpdated","lastEdit","lastEditBy","ipaddress") VALUES (1,'A reply!','A reply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0,'::1');
INSERT INTO "topics"("title","content","parsed_content","createdAt","lastReplyAt","lastReplyBy","createdBy","parentID","ip") VALUES ('Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2,'::1');
INSERT INTO "replies"("tid","content","parsed_content","createdAt","createdBy","lastUpdated","lastEdit","lastEditBy","ip") VALUES (1,'A reply!','A reply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0,'::1');
INSERT INTO "menus"() VALUES ();
INSERT INTO "menu_items"("mid","name","htmlID","position","path","aria","tooltip","order") VALUES (1,'{lang.menu_forums}','menu_forums','left','/forums/','{lang.menu_forums_aria}','{lang.menu_forums_tooltip}',0);
INSERT INTO "menu_items"("mid","name","htmlID","cssClass","position","path","aria","tooltip","order") VALUES (1,'{lang.menu_topics}','menu_topics','menu_topics','left','/topics/','{lang.menu_topics_aria}','{lang.menu_topics_tooltip}',1);

View File

@ -6,5 +6,6 @@ CREATE TABLE "activity_stream" (
`elementType` varchar (50) not null,
`elementID` int not null,
`createdAt` timestamp not null,
`extra` varchar (200) DEFAULT '' not null,
primary key(`asid`)
);

View File

@ -3,5 +3,5 @@ CREATE TABLE "polls_votes" (
`uid` int not null,
`option` int DEFAULT 0 not null,
`castAt` timestamp not null,
`ipaddress` varchar (200) DEFAULT '0.0.0.0.0' not null
`ip` varchar (200) DEFAULT '' not null
);

View File

@ -8,7 +8,7 @@ CREATE TABLE "replies" (
`lastEdit` int DEFAULT 0 not null,
`lastEditBy` int DEFAULT 0 not null,
`lastUpdated` timestamp not null,
`ipaddress` varchar (200) DEFAULT '0.0.0.0.0' not null,
`ip` varchar (200) DEFAULT '' not null,
`likeCount` int DEFAULT 0 not null,
`attachCount` int DEFAULT 0 not null,
`words` int DEFAULT 1 not null,

View File

@ -11,7 +11,7 @@ CREATE TABLE "topics" (
`is_closed` boolean DEFAULT 0 not null,
`sticky` boolean DEFAULT 0 not null,
`parentID` int DEFAULT 2 not null,
`ipaddress` varchar (200) DEFAULT '0.0.0.0.0' not null,
`ip` varchar (200) DEFAULT '' not null,
`postCount` int DEFAULT 1 not null,
`likeCount` int DEFAULT 0 not null,
`attachCount` int DEFAULT 0 not null,

View File

@ -7,6 +7,6 @@ CREATE TABLE "users_replies" (
`createdBy` int not null,
`lastEdit` int DEFAULT 0 not null,
`lastEditBy` int DEFAULT 0 not null,
`ipaddress` varchar (200) DEFAULT '0.0.0.0.0' not null,
`ip` varchar (200) DEFAULT '' not null,
primary key(`rid`)
);

View File

@ -83,7 +83,9 @@
<div class="controls button_container{{if .Topic.LikeCount}} has_likes{{end}}">
<div class="action_button_left">
{{if .CurrentUser.Loggedin}}
{{if .CurrentUser.Perms.LikeItem}}{{if ne .CurrentUser.ID .Topic.CreatedBy}}<a href="/topic/like/submit/{{.Topic.ID}}?s={{.CurrentUser.Session}}" class="action_button like_item {{if .Topic.Liked}}remove{{else}}add{{end}}_like" aria-label="{{lang "topic.like_aria"}}" data-action="like"></a>{{end}}{{end}}
{{if .CurrentUser.Perms.LikeItem}}{{if ne .CurrentUser.ID .Topic.CreatedBy}}
{{if .Topic.Liked}}<a href="/topic/unlike/submit/{{.Topic.ID}}?s={{.CurrentUser.Session}}" class="action_button like_item remove_like" aria-label="{{lang "topic.unlike_aria"}}" data-action="unlike"></a>{{else}}<a href="/topic/like/submit/{{.Topic.ID}}?s={{.CurrentUser.Session}}" class="action_button like_item add_like" aria-label="{{lang "topic.like_aria"}}" data-action="like"></a>{{end}}
{{end}}{{end}}
<a href="" class="action_button quote_item" aria-label="{{lang "topic.quote_aria"}}" data-action="quote"></a>
{{if not .Topic.IsClosed or .CurrentUser.Perms.CloseTopic}}
{{if .CurrentUser.Perms.EditTopic}}<a href="/topic/edit/{{.Topic.ID}}" class="action_button open_edit" aria-label="{{lang "topic.edit_aria"}}" data-action="edit"></a>{{end}}

View File

@ -2,7 +2,6 @@ package main
import (
"database/sql"
"errors"
"log"
"strconv"
"sync/atomic"
@ -10,6 +9,7 @@ import (
c "github.com/Azareal/Gosora/common"
qgen "github.com/Azareal/Gosora/query_gen"
"github.com/pkg/errors"
)
// TODO: Name the tasks so we can figure out which one it was when something goes wrong? Or maybe toss it up WithStack down there?
@ -189,7 +189,7 @@ func dailies() {
if c.Config.DisablePostIP {
f := func(tbl string) {
_, err := qgen.NewAcc().Update(tbl).Set("ipaddress='0'").Where("ipaddress!='0'").Exec()
_, err := qgen.NewAcc().Update(tbl).Set("ip='0'").Where("ip!='0'").Exec()
if err != nil {
c.LogError(err)
}
@ -200,7 +200,7 @@ func dailies() {
} else if c.Config.PostIPCutoff > -1 {
// TODO: Use unixtime to remove this MySQLesque logic?
f := func(tbl string) {
_, err := qgen.NewAcc().Update(tbl).Set("ipaddress='0'").DateOlderThan("createdAt", c.Config.PostIPCutoff, "day").Where("ipaddress!='0'").Exec()
_, err := qgen.NewAcc().Update(tbl).Set("ip='0'").DateOlderThan("createdAt", c.Config.PostIPCutoff, "day").Where("ip!='0'").Exec()
if err != nil {
c.LogError(err)
}
@ -211,13 +211,13 @@ func dailies() {
}
if c.Config.DisablePollIP {
_, err := qgen.NewAcc().Update("polls_votes").Set("ipaddress='0'").Where("ipaddress!='0'").Exec()
_, err := qgen.NewAcc().Update("polls_votes").Set("ip='0'").Where("ip!='0'").Exec()
if err != nil {
c.LogError(err)
}
} else if c.Config.PollIPCutoff > -1 {
// TODO: Use unixtime to remove this MySQLesque logic?
_, err := qgen.NewAcc().Update("polls_votes").Set("ipaddress='0'").DateOlderThan("castAt", c.Config.PollIPCutoff, "day").Where("ipaddress!='0'").Exec()
_, err := qgen.NewAcc().Update("polls_votes").Set("ip='0'").DateOlderThan("castAt", c.Config.PollIPCutoff, "day").Where("ip!='0'").Exec()
if err != nil {
c.LogError(err)
}
@ -237,7 +237,7 @@ func dailies() {
c.LogError(err)
}*/
mon := time.Now().Month()
_, err := qgen.NewAcc().Update("users").Set("last_ip=0").Where("last_ip!=0 AND last_ip NOT LIKE '" + strconv.Itoa(int(mon)) + "-%'").Exec()
_, err := qgen.NewAcc().Update("users").Set("last_ip=0").Where("last_ip!='0' AND last_ip NOT LIKE '" + strconv.Itoa(int(mon)) + "-%'").Exec()
if err != nil {
c.LogError(err)
}
@ -250,3 +250,47 @@ func dailies() {
}
}
}
func sched() error {
schedStr, err := c.Meta.Get("sched")
// TODO: Report this error back correctly...
if err != nil && err != sql.ErrNoRows {
return errors.WithStack(err)
}
if schedStr == "recalc" {
log.Print("Cleaning up orphaned data.")
count, err := c.Recalc.Replies()
if err != nil {
return errors.WithStack(err)
}
log.Printf("Deleted %d orphaned replies.", count)
count, err = c.Recalc.Subscriptions()
if err != nil {
return errors.WithStack(err)
}
log.Printf("Deleted %d orphaned subscriptions.", count)
count, err = c.Recalc.ActivityStream()
if err != nil {
return errors.WithStack(err)
}
log.Printf("Deleted %d orphaned activity stream items.", count)
err = c.Recalc.Users()
if err != nil {
return errors.WithStack(err)
}
log.Print("Recalculated user post stats.")
count, err = c.Recalc.Attachments()
if err != nil {
return errors.WithStack(err)
}
log.Printf("Deleted %d orphaned attachments.", count)
}
return nil
}