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:
parent
e1702687f7
commit
6935637867
|
@ -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 {
|
||||
|
|
|
@ -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", "", "")
|
||||
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
@ -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)+`}`)
|
||||
}
|
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
|
@ -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(),
|
||||
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
|
||||
|
@ -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
|
||||
|
|
|
@ -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,7 +21,7 @@ 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"
|
||||
|
@ -44,7 +44,7 @@ func SendValidationEmail(username string, email string, token string) error {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
|
|
|
@ -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
|
||||
|
@ -72,7 +72,7 @@ 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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -10,16 +10,19 @@ 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()
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"strconv"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/Azareal/Gosora/query_gen"
|
||||
qgen "github.com/Azareal/Gosora/query_gen"
|
||||
)
|
||||
|
||||
var Menus *DefaultMenuStore
|
||||
|
|
|
@ -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)
|
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,10 +66,9 @@ func InitPluginLangs() error {
|
|||
continue
|
||||
}
|
||||
|
||||
e := func(field string, name string) error {
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ 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(),
|
||||
//count: acc.SimpleCount("polls", "", ""),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 Countf(s.count)
|
||||
}
|
||||
return 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) {
|
||||
|
|
|
@ -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(),
|
||||
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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,7 +217,7 @@ 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", "?,?"),
|
||||
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(),
|
||||
|
@ -227,15 +229,15 @@ func init() {
|
|||
createLike: acc.Insert("likes").Columns("weight, targetItem, targetType, sentBy, createdAt").Fields("?,?,?,?,UTC_TIMESTAMP()").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
|
||||
}
|
||||
|
@ -372,16 +398,30 @@ 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
|
||||
}
|
||||
|
|
|
@ -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 Countf(s.count)
|
||||
}
|
||||
return 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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
@ -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
|
||||
|
|
214
gen_router.go
214
gen_router.go
|
@ -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
|
||||
|
|
|
@ -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
13
main.go
|
@ -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
|
||||
|
|
18
misc_test.go
18
misc_test.go
|
@ -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) {
|
||||
|
|
|
@ -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))
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
if column.Size > 0 {
|
||||
size = "(" + strconv.Itoa(column.Size) + ")"
|
||||
|
||||
// ! 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 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
|
||||
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) + "',"
|
||||
|
|
|
@ -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"),
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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])
|
||||
);
|
|
@ -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
|
||||
);
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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])
|
||||
);
|
|
@ -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);
|
||||
|
|
|
@ -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`)
|
||||
);
|
|
@ -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;
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
|
@ -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);
|
||||
|
|
|
@ -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`)
|
||||
);
|
|
@ -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
|
||||
);
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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`)
|
||||
);
|
|
@ -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}}
|
||||
|
|
56
tickloop.go
56
tickloop.go
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue