Experimenting with speeding up the installer.

Added support for foreign keys to the MySQL adapter.
activity_stream_matches now has a foreign key to help enforce referential integrity.
Added the AddForeignKey method to the database adapters.
Shortened a couple of API bits to ever slow slightly reduce the lengths of the strings.

Fixed a phrase group I missed for logged out users in init.js
Fixed a bug where deleting a topic would break the alert list when there is an alert event relating to it.

You will need to run the updater / patcher for this commit.
This commit is contained in:
Azareal 2019-05-06 14:04:00 +10:00
parent 634b03936c
commit 839df17de3
20 changed files with 339 additions and 102 deletions

View File

@ -14,7 +14,7 @@ func NewPrimaryKeySpitter() *PrimaryKeySpitter {
func (spit *PrimaryKeySpitter) Hook(name string, args ...interface{}) error { func (spit *PrimaryKeySpitter) Hook(name string, args ...interface{}) error {
if name == "CreateTableStart" { if name == "CreateTableStart" {
var found string var found string
var table = args[0].(*qgen.DB_Install_Table) var table = args[0].(*qgen.DBInstallTable)
for _, key := range table.Keys { for _, key := range table.Keys {
if key.Type == "primary" { if key.Type == "primary" {
expl := strings.Split(key.Columns, ",") expl := strings.Split(key.Columns, ",")

View File

@ -45,8 +45,8 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"temp_group", "int", 0, false, false, "0"}, // For temporary groups, set this to zero when a temporary group isn't in effect tblColumn{"temp_group", "int", 0, false, false, "0"}, // For temporary groups, set this to zero when a temporary group isn't in effect
}, },
[]tblKey{ []tblKey{
tblKey{"uid", "primary"}, tblKey{"uid", "primary","",false},
tblKey{"name", "unique"}, tblKey{"name", "unique","",false},
}, },
) )
@ -64,7 +64,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"tag", "varchar", 50, false, false, "''"}, tblColumn{"tag", "varchar", 50, false, false, "''"},
}, },
[]tblKey{ []tblKey{
tblKey{"gid", "primary"}, tblKey{"gid", "primary","",false},
}, },
) )
@ -83,7 +83,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"createdAt", "createdAt", 0, false, false, ""}, tblColumn{"createdAt", "createdAt", 0, false, false, ""},
}, },
[]tblKey{ []tblKey{
tblKey{"uid", "primary"}, tblKey{"uid", "primary","",false},
}, },
) )
@ -128,7 +128,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"temporary", "boolean", 0, false, false, ""}, // special case for permanent bans to do the necessary bookkeeping, might be removed in the future tblColumn{"temporary", "boolean", 0, false, false, ""}, // special case for permanent bans to do the necessary bookkeeping, might be removed in the future
}, },
[]tblKey{ []tblKey{
tblKey{"uid", "primary"}, tblKey{"uid", "primary","",false},
}, },
) )
@ -138,7 +138,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key tblColumn{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
}, },
[]tblKey{ []tblKey{
tblKey{"uid", "primary"}, tblKey{"uid", "primary","",false},
}, },
) )
@ -191,7 +191,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"lastReplyerID", "int", 0, false, false, "0"}, tblColumn{"lastReplyerID", "int", 0, false, false, "0"},
}, },
[]tblKey{ []tblKey{
tblKey{"fid", "primary"}, tblKey{"fid", "primary","",false},
}, },
) )
@ -204,7 +204,7 @@ func createTables(adapter qgen.Adapter) error {
}, },
[]tblKey{ []tblKey{
// TODO: Test to see that the compound primary key works // TODO: Test to see that the compound primary key works
tblKey{"fid,gid", "primary"}, tblKey{"fid,gid", "primary","",false},
}, },
) )
@ -240,8 +240,8 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"data", "varchar", 200, false, false, "''"}, tblColumn{"data", "varchar", 200, false, false, "''"},
}, },
[]tblKey{ []tblKey{
tblKey{"tid", "primary"}, tblKey{"tid", "primary","",false},
tblKey{"content", "fulltext"}, tblKey{"content", "fulltext","",false},
}, },
) )
@ -264,8 +264,8 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"poll", "int", 0, false, false, "0"}, tblColumn{"poll", "int", 0, false, false, "0"},
}, },
[]tblKey{ []tblKey{
tblKey{"rid", "primary"}, tblKey{"rid", "primary","",false},
tblKey{"content", "fulltext"}, tblKey{"content", "fulltext","",false},
}, },
) )
@ -281,7 +281,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"extra", "varchar", 200, false, false, ""}, tblColumn{"extra", "varchar", 200, false, false, ""},
}, },
[]tblKey{ []tblKey{
tblKey{"attachID", "primary"}, tblKey{"attachID", "primary","",false},
}, },
) )
@ -295,7 +295,7 @@ func createTables(adapter qgen.Adapter) error {
// TODO: Add a createdBy column? // TODO: Add a createdBy column?
}, },
[]tblKey{ []tblKey{
tblKey{"reviseID", "primary"}, tblKey{"reviseID", "primary","",false},
}, },
) )
@ -309,7 +309,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"votes", "int", 0, false, false, "0"}, tblColumn{"votes", "int", 0, false, false, "0"},
}, },
[]tblKey{ []tblKey{
tblKey{"pollID", "primary"}, tblKey{"pollID", "primary","",false},
}, },
) )
@ -344,7 +344,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"ipaddress", "varchar", 200, false, false, "0.0.0.0.0"}, tblColumn{"ipaddress", "varchar", 200, false, false, "0.0.0.0.0"},
}, },
[]tblKey{ []tblKey{
tblKey{"rid", "primary"}, tblKey{"rid", "primary","",false},
}, },
) )
@ -363,7 +363,10 @@ func createTables(adapter qgen.Adapter) error {
[]tblColumn{ []tblColumn{
tblColumn{"watcher", "int", 0, false, false, ""}, // TODO: Make this a foreign key tblColumn{"watcher", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tblColumn{"asid", "int", 0, false, false, ""}, // TODO: Make this a foreign key tblColumn{"asid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
}, nil, },
[]tblKey{
tblKey{"asid,asid","foreign","activity_stream",true},
},
) )
qgen.Install.CreateTable("activity_stream", "", "", qgen.Install.CreateTable("activity_stream", "", "",
@ -376,7 +379,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"elementID", "int", 0, false, false, ""}, /* the ID of the element being acted upon */ tblColumn{"elementID", "int", 0, false, false, ""}, /* the ID of the element being acted upon */
}, },
[]tblKey{ []tblKey{
tblKey{"asid", "primary"}, tblKey{"asid", "primary","",false},
}, },
) )
@ -398,7 +401,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"constraints", "varchar", 200, false, false, "''"}, tblColumn{"constraints", "varchar", 200, false, false, "''"},
}, },
[]tblKey{ []tblKey{
tblKey{"name", "unique"}, tblKey{"name", "unique","",false},
}, },
) )
@ -409,7 +412,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"replacement", "varchar", 200, false, false, ""}, tblColumn{"replacement", "varchar", 200, false, false, ""},
}, },
[]tblKey{ []tblKey{
tblKey{"wfid", "primary"}, tblKey{"wfid", "primary","",false},
}, },
) )
@ -420,7 +423,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"installed", "boolean", 0, false, false, "0"}, tblColumn{"installed", "boolean", 0, false, false, "0"},
}, },
[]tblKey{ []tblKey{
tblKey{"uname", "unique"}, tblKey{"uname", "unique","",false},
}, },
) )
@ -431,7 +434,7 @@ func createTables(adapter qgen.Adapter) error {
//tblColumn{"profileUserVars", "text", 0, false, false, "''"}, //tblColumn{"profileUserVars", "text", 0, false, false, "''"},
}, },
[]tblKey{ []tblKey{
tblKey{"uname", "unique"}, tblKey{"uname", "unique","",false},
}, },
) )
@ -446,7 +449,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"data", "text", 0, false, false, "''"}, tblColumn{"data", "text", 0, false, false, "''"},
}, },
[]tblKey{ []tblKey{
tblKey{"wid", "primary"}, tblKey{"wid", "primary","",false},
}, },
) )
@ -455,7 +458,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"mid", "int", 0, false, true, ""}, tblColumn{"mid", "int", 0, false, true, ""},
}, },
[]tblKey{ []tblKey{
tblKey{"mid", "primary"}, tblKey{"mid", "primary","",false},
}, },
) )
@ -479,7 +482,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"adminOnly", "boolean", 0, false, false, "0"}, tblColumn{"adminOnly", "boolean", 0, false, false, "0"},
}, },
[]tblKey{ []tblKey{
tblKey{"miid", "primary"}, tblKey{"miid", "primary","",false},
}, },
) )
@ -495,7 +498,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"menuID", "int", 0, false, false, "-1"}, // simple sidebar menu tblColumn{"menuID", "int", 0, false, false, "-1"}, // simple sidebar menu
}, },
[]tblKey{ []tblKey{
tblKey{"pid", "primary"}, tblKey{"pid", "primary","",false},
}, },
) )
@ -510,7 +513,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"doneAt", "createdAt", 0, false, false, ""}, tblColumn{"doneAt", "createdAt", 0, false, false, ""},
}, },
[]tblKey{ []tblKey{
tblKey{"rlid", "primary"}, tblKey{"rlid", "primary","",false},
}, },
) )
@ -523,7 +526,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"doneAt", "createdAt", 0, false, false, ""}, tblColumn{"doneAt", "createdAt", 0, false, false, ""},
}, },
[]tblKey{ []tblKey{
tblKey{"lid", "primary"}, tblKey{"lid", "primary","",false},
}, },
) )

View File

@ -180,6 +180,8 @@ type TopicStmts struct {
createLike *sql.Stmt createLike *sql.Stmt
addLikesToTopic *sql.Stmt addLikesToTopic *sql.Stmt
delete *sql.Stmt delete *sql.Stmt
deleteActivity *sql.Stmt
deleteActivitySubs *sql.Stmt
edit *sql.Stmt edit *sql.Stmt
setPoll *sql.Stmt setPoll *sql.Stmt
createAction *sql.Stmt createAction *sql.Stmt
@ -204,6 +206,8 @@ func init() {
createLike: acc.Insert("likes").Columns("weight, targetItem, targetType, sentBy, createdAt").Fields("?,?,?,?,UTC_TIMESTAMP()").Prepare(), createLike: acc.Insert("likes").Columns("weight, targetItem, targetType, sentBy, createdAt").Fields("?,?,?,?,UTC_TIMESTAMP()").Prepare(),
addLikesToTopic: acc.Update("topics").Set("likeCount = likeCount + ?").Where("tid = ?").Prepare(), addLikesToTopic: acc.Update("topics").Set("likeCount = likeCount + ?").Where("tid = ?").Prepare(),
delete: acc.Delete("topics").Where("tid = ?").Prepare(), delete: acc.Delete("topics").Where("tid = ?").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("topics").Set("title = ?, content = ?, parsed_content = ?").Where("tid = ?").Prepare(), // TODO: Only run the content update bits on non-polls, does this matter? edit: acc.Update("topics").Set("title = ?, content = ?, parsed_content = ?").Where("tid = ?").Prepare(), // TODO: Only run the content update bits on non-polls, does this matter?
setPoll: acc.Update("topics").Set("content = '', parsed_content = '', poll = ?").Where("tid = ? AND poll = 0").Prepare(), setPoll: acc.Update("topics").Set("content = '', parsed_content = '', 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, ipaddress, createdBy, createdAt, lastUpdated, content, parsed_content").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),'',''").Prepare(),
@ -325,6 +329,14 @@ func (topic *Topic) Delete() error {
_, err = topicStmts.delete.Exec(topic.ID) _, err = topicStmts.delete.Exec(topic.ID)
topic.cacheRemove() topic.cacheRemove()
if err != nil {
return err
}
_, err = topicStmts.deleteActivitySubs.Exec(topic.ID)
if err != nil {
return err
}
_, err = topicStmts.deleteActivity.Exec(topic.ID)
return err return err
} }

View File

@ -10,10 +10,12 @@ import (
"bytes" "bytes"
"database/sql" "database/sql"
"fmt" "fmt"
"os"
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"sync"
"github.com/Azareal/Gosora/query_gen" "github.com/Azareal/Gosora/query_gen"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
@ -93,53 +95,140 @@ func (ins *MysqlInstaller) InitDatabase() (err error) {
fmt.Println("The database was successfully created") fmt.Println("The database was successfully created")
} }
fmt.Println("Switching to database ", ins.dbName) /*fmt.Println("Switching to database ", ins.dbName)
_, err = db.Exec("USE " + ins.dbName) _, err = db.Exec("USE " + ins.dbName)
if err != nil {
return err
}*/
db.Close()
db, err = sql.Open("mysql", ins.dbUsername+_dbPassword+"@tcp("+ins.dbHost+":"+ins.dbPort+")/" + ins.dbName)
if err != nil { if err != nil {
return err return err
} }
// Make sure that the connection is alive..
err = db.Ping()
if err != nil {
return err
}
fmt.Println("Successfully connected to the database")
// Ready the query builder // Ready the query builder
ins.db = db
qgen.Builder.SetConn(db) qgen.Builder.SetConn(db)
return qgen.Builder.SetAdapter("mysql") return qgen.Builder.SetAdapter("mysql")
} }
func(ins *MysqlInstaller) createTable(f os.FileInfo) error {
table := strings.TrimPrefix(f.Name(), "query_")
ext := filepath.Ext(table)
if ext != ".sql" {
return nil
}
table = strings.TrimSuffix(table, ext)
// ? - This is mainly here for tests, although it might allow the installer to overwrite a production database, so we might want to proceed with caution
q := "DROP TABLE IF EXISTS `" + table + "`;"
_, err := ins.db.Exec(q)
if err != nil {
fmt.Println("Failed query:", q)
fmt.Println("e:",err)
return err
}
data, err := ioutil.ReadFile("./schema/mysql/" + f.Name())
if err != nil {
return err
}
data = bytes.TrimSpace(data)
_, err = ins.db.Exec(string(data))
if err != nil {
fmt.Println("Failed query:", string(data))
fmt.Println("e:",err)
return err
}
fmt.Printf("Created table '%s'\n", table)
return nil
}
func (ins *MysqlInstaller) TableDefs() (err error) { func (ins *MysqlInstaller) TableDefs() (err error) {
fmt.Println("Creating the tables") fmt.Println("Creating the tables")
files, _ := ioutil.ReadDir("./schema/mysql/") files, err := ioutil.ReadDir("./schema/mysql/")
for _, f := range files { if err != nil {
return err
}
// TODO: Can we reduce the amount of boilerplate here?
after := []string{"activity_stream_matches"}
c1 := make(chan os.FileInfo)
c2 := make(chan os.FileInfo)
e := make(chan error)
var wg sync.WaitGroup
r := func(c chan os.FileInfo) {
wg.Add(1)
for f := range c {
err := ins.createTable(f)
if err != nil {
e <- err
}
}
wg.Done()
}
go r(c1)
go r(c2)
var a []os.FileInfo
Outer:
for i, f := range files {
if !strings.HasPrefix(f.Name(), "query_") { if !strings.HasPrefix(f.Name(), "query_") {
continue continue
} }
table := strings.TrimPrefix(f.Name(), "query_")
var table, ext string ext := filepath.Ext(table)
table = strings.TrimPrefix(f.Name(), "query_")
ext = filepath.Ext(table)
if ext != ".sql" { if ext != ".sql" {
continue continue
} }
table = strings.TrimSuffix(table, ext) table = strings.TrimSuffix(table, ext)
for _, tbl := range after {
// ? - This is mainly here for tests, although it might allow the installer to overwrite a production database, so we might want to proceed with caution if tbl == table {
_, err = ins.db.Exec("DROP TABLE IF EXISTS `" + table + "`;") a = append(a, f)
if err != nil { continue Outer
fmt.Println("Failed query:", "DROP TABLE IF EXISTS `"+table+"`;") }
return err
} }
if i%2 == 0 {
fmt.Printf("Creating table '%s'\n", table) c1 <- f
data, err := ioutil.ReadFile("./schema/mysql/" + f.Name()) } else {
if err != nil { c2 <- f
return err
} }
data = bytes.TrimSpace(data) }
close(c1)
close(c2)
wg.Wait()
close(e)
_, err = ins.db.Exec(string(data)) var first error
for err := range e {
if first == nil {
first = err
}
}
if first != nil {
return first
}
for _, f := range a {
if !strings.HasPrefix(f.Name(), "query_") {
continue
}
err := ins.createTable(f)
if err != nil { if err != nil {
fmt.Println("Failed query:", string(data))
return err return err
} }
} }
return nil return nil
} }

View File

@ -32,6 +32,7 @@ func init() {
addPatch(17, patch17) addPatch(17, patch17)
addPatch(18, patch18) addPatch(18, patch18)
addPatch(19, patch19) addPatch(19, patch19)
addPatch(20, patch20)
} }
func patch0(scanner *bufio.Scanner) (err error) { func patch0(scanner *bufio.Scanner) (err error) {
@ -49,7 +50,7 @@ func patch0(scanner *bufio.Scanner) (err error) {
tblColumn{"mid", "int", 0, false, true, ""}, tblColumn{"mid", "int", 0, false, true, ""},
}, },
[]tblKey{ []tblKey{
tblKey{"mid", "primary"}, tblKey{"mid", "primary","",false},
}, },
)) ))
if err != nil { if err != nil {
@ -76,7 +77,7 @@ func patch0(scanner *bufio.Scanner) (err error) {
tblColumn{"adminOnly", "boolean", 0, false, false, "0"}, tblColumn{"adminOnly", "boolean", 0, false, false, "0"},
}, },
[]tblKey{ []tblKey{
tblKey{"miid", "primary"}, tblKey{"miid", "primary","",false},
}, },
)) ))
if err != nil { if err != nil {
@ -183,7 +184,7 @@ func patch3(scanner *bufio.Scanner) error {
tblColumn{"doneAt", "createdAt", 0, false, false, ""}, tblColumn{"doneAt", "createdAt", 0, false, false, ""},
}, },
[]tblKey{ []tblKey{
tblKey{"rlid", "primary"}, tblKey{"rlid", "primary","",false},
}, },
)) ))
} }
@ -246,7 +247,7 @@ func patch4(scanner *bufio.Scanner) error {
tblColumn{"menuID", "int", 0, false, false, "-1"}, tblColumn{"menuID", "int", 0, false, false, "-1"},
}, },
[]tblKey{ []tblKey{
tblKey{"pid", "primary"}, tblKey{"pid", "primary","",false},
}, },
)) ))
if err != nil { if err != nil {
@ -289,7 +290,7 @@ func patch5(scanner *bufio.Scanner) error {
tblColumn{"createdAt", "createdAt", 0, false, false, ""}, tblColumn{"createdAt", "createdAt", 0, false, false, ""},
}, },
[]tblKey{ []tblKey{
tblKey{"uid", "primary"}, tblKey{"uid", "primary","",false},
}, },
)) ))
if err != nil { if err != nil {
@ -309,7 +310,7 @@ func patch7(scanner *bufio.Scanner) error {
tblColumn{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key tblColumn{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
}, },
[]tblKey{ []tblKey{
tblKey{"uid", "primary"}, tblKey{"uid", "primary","",false},
}, },
)) ))
} }
@ -391,7 +392,7 @@ func patch9(scanner *bufio.Scanner) error {
tblColumn{"doneAt", "createdAt", 0, false, false, ""}, tblColumn{"doneAt", "createdAt", 0, false, false, ""},
}, },
[]tblKey{ []tblKey{
tblKey{"lid", "primary"}, tblKey{"lid", "primary","",false},
}, },
)) ))
} }
@ -514,7 +515,7 @@ func patch12(scanner *bufio.Scanner) error {
} }
func patch13(scanner *bufio.Scanner) error { func patch13(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.AddColumn("widgets", tblColumn{"wid", "int", 0, false, true, ""}, &tblKey{"wid", "primary"})) err := execStmt(qgen.Builder.AddColumn("widgets", tblColumn{"wid", "int", 0, false, true, ""}, &tblKey{"wid", "primary","",false}))
if err != nil { if err != nil {
return err return err
} }
@ -523,15 +524,15 @@ func patch13(scanner *bufio.Scanner) error {
} }
func patch14(scanner *bufio.Scanner) error { func patch14(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.AddKey("topics", "title", tblKey{"title", "fulltext"})) err := execStmt(qgen.Builder.AddKey("topics", "title", tblKey{"title", "fulltext","",false}))
if err != nil { if err != nil {
return err return err
} }
err = execStmt(qgen.Builder.AddKey("topics", "content", tblKey{"content", "fulltext"})) err = execStmt(qgen.Builder.AddKey("topics", "content", tblKey{"content", "fulltext","",false}))
if err != nil { if err != nil {
return err return err
} }
err = execStmt(qgen.Builder.AddKey("replies", "content", tblKey{"content", "fulltext"})) err = execStmt(qgen.Builder.AddKey("replies", "content", tblKey{"content", "fulltext","",false}))
if err != nil { if err != nil {
return err return err
} }
@ -603,3 +604,26 @@ func patch19(scanner *bufio.Scanner) error {
}, nil, }, nil,
)) ))
} }
func patch20(scanner *bufio.Scanner) error {
err := acc().Select("activity_stream_matches").Cols("asid").Each(func(rows *sql.Rows) error {
var asid int
err := rows.Scan(&asid)
if err != nil {
return err
}
err = acc().Select("activity_stream").Cols("asid").Where("asid = ?").QueryRow(asid).Scan(&asid)
if err != sql.ErrNoRows {
return err
}
_, err = acc().Delete("activity_stream_matches").Where("asid = ?").Run(asid)
return err
})
if err != nil {
return err
}
return execStmt(qgen.Builder.AddForeignKey("activity_stream_matches", "asid","activity_stream","asid",true))
}

View File

@ -138,7 +138,7 @@ function loadAlerts(menuAlerts) {
$.ajax({ $.ajax({
type: 'get', type: 'get',
dataType: 'json', dataType: 'json',
url:'/api/?action=get&module=alerts', url:'/api/?module=alerts',
success: (data) => { success: (data) => {
if("errmsg" in data) { if("errmsg" in data) {
setAlertError(menuAlerts,data.errmsg) setAlertError(menuAlerts,data.errmsg)
@ -147,8 +147,8 @@ function loadAlerts(menuAlerts) {
alertList = []; alertList = [];
alertMapping = {}; alertMapping = {};
for(var i in data.msgs) addAlert(data.msgs[i]); for(var i in data.msgs) addAlert(data.msgs[i]);
console.log("data.msgCount:",data.msgCount) console.log("data.count:",data.count)
alertCount = data.msgCount; alertCount = data.count;
updateAlertList(menuAlerts) updateAlertList(menuAlerts)
}, },
error: (magic,theStatus,error) => { error: (magic,theStatus,error) => {

View File

@ -178,7 +178,7 @@ function initPhrases(loggedIn, panel = false) {
console.log("in initPhrases") console.log("in initPhrases")
console.log("tmlInits:",tmplInits) console.log("tmlInits:",tmplInits)
let e = ""; let e = "";
if(loggedIn && !panel) e = ",topic_list,topic"; if(loggedIn && !panel) e = ",status,topic_list,topic";
else if(panel) e = ",analytics,panel"; // TODO: Request phrases for just one section of the control panel? else if(panel) e = ",analytics,panel"; // TODO: Request phrases for just one section of the control panel?
else e = ",status,topic_list"; else e = ",status,topic_list";
fetchPhrases("alerts,paginator"+e) // TODO: Break this up? fetchPhrases("alerts,paginator"+e) // TODO: Break this up?

View File

@ -120,6 +120,10 @@ func (build *builder) AddKey(table string, column string, key DBTableKey) (stmt
return build.prepare(build.adapter.AddKey("", table, column, key)) return build.prepare(build.adapter.AddKey("", table, column, key))
} }
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 (build *builder) SimpleInsert(table string, columns string, fields string) (stmt *sql.Stmt, err error) { func (build *builder) SimpleInsert(table string, columns string, fields string) (stmt *sql.Stmt, err error) {
return build.prepare(build.adapter.SimpleInsert("", table, columns, fields)) return build.prepare(build.adapter.SimpleInsert("", table, columns, fields))
} }

View File

@ -3,17 +3,17 @@ package qgen
var Install *installer var Install *installer
func init() { func init() {
Install = &installer{instructions: []DB_Install_Instruction{}} Install = &installer{instructions: []DBInstallInstruction{}}
} }
type DB_Install_Instruction struct { type DBInstallInstruction struct {
Table string Table string
Contents string Contents string
Type string Type string
} }
// TODO: Add methods to this to construct it OO-like // TODO: Add methods to this to construct it OO-like
type DB_Install_Table struct { type DBInstallTable struct {
Name string Name string
Charset string Charset string
Collation string Collation string
@ -25,8 +25,8 @@ type DB_Install_Table struct {
// TODO: Re-implement the query generation, query builder and installer adapters as layers on-top of a query text adapter // TODO: Re-implement the query generation, query builder and installer adapters as layers on-top of a query text adapter
type installer struct { type installer struct {
adapter Adapter adapter Adapter
instructions []DB_Install_Instruction instructions []DBInstallInstruction
tables []*DB_Install_Table // TODO: Use this in Record() in the next commit to allow us to auto-migrate settings rather than manually patching them in on upgrade tables []*DBInstallTable // TODO: Use this in Record() in the next commit to allow us to auto-migrate settings rather than manually patching them in on upgrade
plugins []QueryPlugin plugins []QueryPlugin
} }
@ -41,7 +41,7 @@ func (install *installer) SetAdapter(name string) error {
func (install *installer) SetAdapterInstance(adapter Adapter) { func (install *installer) SetAdapterInstance(adapter Adapter) {
install.adapter = adapter install.adapter = adapter
install.instructions = []DB_Install_Instruction{} install.instructions = []DBInstallInstruction{}
} }
func (install *installer) AddPlugins(plugins ...QueryPlugin) { func (install *installer) AddPlugins(plugins ...QueryPlugin) {
@ -49,7 +49,7 @@ func (install *installer) AddPlugins(plugins ...QueryPlugin) {
} }
func (install *installer) CreateTable(table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) error { func (install *installer) CreateTable(table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) error {
tableStruct := &DB_Install_Table{table, charset, collation, columns, keys} tableStruct := &DBInstallTable{table, charset, collation, columns, keys}
err := install.RunHook("CreateTableStart", tableStruct) err := install.RunHook("CreateTableStart", tableStruct)
if err != nil { if err != nil {
return err return err
@ -62,7 +62,7 @@ func (install *installer) CreateTable(table string, charset string, collation st
if err != nil { if err != nil {
return err return err
} }
install.instructions = append(install.instructions, DB_Install_Instruction{table, res, "create-table"}) install.instructions = append(install.instructions, DBInstallInstruction{table, res, "create-table"})
install.tables = append(install.tables, tableStruct) install.tables = append(install.tables, tableStruct)
return nil return nil
} }
@ -81,7 +81,7 @@ func (install *installer) AddIndex(table string, iname string, colname string) e
if err != nil { if err != nil {
return err return err
} }
install.instructions = append(install.instructions, DB_Install_Instruction{table, res, "index"}) install.instructions = append(install.instructions, DBInstallInstruction{table, res, "index"})
return nil return nil
} }
@ -99,7 +99,7 @@ func (install *installer) SimpleInsert(table string, columns string, fields stri
if err != nil { if err != nil {
return err return err
} }
install.instructions = append(install.instructions, DB_Install_Instruction{table, res, "insert"}) install.instructions = append(install.instructions, DBInstallInstruction{table, res, "insert"})
return nil return nil
} }

View File

@ -53,6 +53,7 @@ func (adapter *MssqlAdapter) DropTable(name string, table string) (string, error
return querystr, nil return querystr, nil
} }
// TODO: Add support for foreign keys?
// TODO: Convert any remaining stringy types to nvarchar // 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 // We may need to change the CreateTable API to better suit Mssql and the other database drivers which are coming up
func (adapter *MssqlAdapter) CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) { func (adapter *MssqlAdapter) CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) {
@ -174,6 +175,25 @@ func (adapter *MssqlAdapter) AddKey(name string, table string, column string, ke
return "", errors.New("not implemented") return "", errors.New("not implemented")
} }
// TODO: Implement this
// TODO: Test to make sure everything works here
func (adapter *MssqlAdapter) AddForeignKey(name string, table string, column string, ftable string, fcolumn string, cascade bool) (out string, e error) {
var c = func(str string, val bool) {
if e != nil || !val {
return
}
e = errors.New("You need a "+str+" for this table")
}
c("name",table=="")
c("column",column=="")
c("ftable",ftable=="")
c("fcolumn",fcolumn=="")
if e != nil {
return "", e
}
return "", errors.New("not implemented")
}
func (adapter *MssqlAdapter) SimpleInsert(name string, table string, columns string, fields string) (string, error) { func (adapter *MssqlAdapter) SimpleInsert(name string, table string, columns string, fields string) (string, error) {
if table == "" { if table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")

View File

@ -112,11 +112,20 @@ func (adapter *MysqlAdapter) CreateTable(name string, table string, charset stri
if key.Type != "unique" { if key.Type != "unique" {
querystr += " key" querystr += " key"
} }
querystr += "(" if key.Type == "foreign" {
for _, column := range strings.Split(key.Columns, ",") { cols := strings.Split(key.Columns, ",")
querystr += "`" + column + "`," querystr += "(`" + cols[0] + "`) REFERENCES `" + key.FTable + "`(`" + cols[1] + "`)"
if key.Cascade {
querystr += " ON DELETE CASCADE"
}
querystr += ","
} else {
querystr += "("
for _, column := range strings.Split(key.Columns, ",") {
querystr += "`" + column + "`,"
}
querystr = querystr[0:len(querystr)-1] + "),"
} }
querystr = querystr[0:len(querystr)-1] + "),"
} }
} }
@ -173,12 +182,12 @@ func (adapter *MysqlAdapter) parseColumn(column DBTableColumn) (col DBTableColum
// TODO: Support AFTER column // TODO: Support AFTER column
// TODO: Test to make sure everything works here // TODO: Test to make sure everything works here
func (adapter *MysqlAdapter) AddColumn(name string, table string, column DBTableColumn, key *DBTableKey) (string, error) { func (a *MysqlAdapter) AddColumn(name string, table string, column DBTableColumn, key *DBTableKey) (string, error) {
if table == "" { if table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
column, size, end := adapter.parseColumn(column) column, size, end := a.parseColumn(column)
querystr := "ALTER TABLE `" + table + "` ADD COLUMN " + "`" + column.Name + "` " + column.Type + size + end querystr := "ALTER TABLE `" + table + "` ADD COLUMN " + "`" + column.Name + "` " + column.Type + size + end
if key != nil { if key != nil {
@ -191,12 +200,12 @@ func (adapter *MysqlAdapter) AddColumn(name string, table string, column DBTable
} }
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator // TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
adapter.pushStatement(name, "add-column", querystr) a.pushStatement(name, "add-column", querystr)
return querystr, nil return querystr, nil
} }
// TODO: Test to make sure everything works here // TODO: Test to make sure everything works here
func (adapter *MysqlAdapter) AddIndex(name string, table string, iname string, colname string) (string, error) { func (a *MysqlAdapter) AddIndex(name string, table string, iname string, colname string) (string, error) {
if table == "" { if table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
@ -209,23 +218,50 @@ func (adapter *MysqlAdapter) AddIndex(name string, table string, iname string, c
querystr := "ALTER TABLE `" + table + "` ADD INDEX " + "`" + iname + "` (`" + colname + "`);" querystr := "ALTER TABLE `" + table + "` ADD INDEX " + "`" + iname + "` (`" + colname + "`);"
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator // TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
adapter.pushStatement(name, "add-index", querystr) a.pushStatement(name, "add-index", querystr)
return querystr, nil return querystr, nil
} }
// TODO: Test to make sure everything works here // TODO: Test to make sure everything works here
// Only supports FULLTEXT right now // Only supports FULLTEXT right now
func (adapter *MysqlAdapter) AddKey(name string, table string, column string, key DBTableKey) (string, error) { func (a *MysqlAdapter) AddKey(name string, table string, column string, key DBTableKey) (string, error) {
if table == "" { if table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
if key.Type != "fulltext" { var querystr string
if key.Type == "fulltext" {
querystr = "ALTER TABLE `" + table + "` ADD FULLTEXT(`" + column + "`)"
} else {
return "", errors.New("Only fulltext is supported by AddKey right now") return "", errors.New("Only fulltext is supported by AddKey right now")
} }
querystr := "ALTER TABLE `" + table + "` ADD FULLTEXT(`" + column + "`)"
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator // TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
adapter.pushStatement(name, "add-key", querystr) a.pushStatement(name, "add-key", querystr)
return querystr, nil
}
func (a *MysqlAdapter) AddForeignKey(name string, table string, column string, ftable string, fcolumn string, cascade bool) (out string, e error) {
var c = func(str string, val bool) {
if e != nil || !val {
return
}
e = errors.New("You need a "+str+" for this table")
}
c("name",table=="")
c("column",column=="")
c("ftable",ftable=="")
c("fcolumn",fcolumn=="")
if e != nil {
return "", e
}
querystr := "ALTER TABLE `"+table+"` ADD CONSTRAINT `fk_"+column+"` FOREIGN KEY(`"+column+"`) REFERENCES `"+ftable+"`(`"+fcolumn+"`)"
if cascade {
querystr += " ON DELETE CASCADE"
}
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
a.pushStatement(name, "add-foreign-key", querystr)
return querystr, nil return querystr, nil
} }

View File

@ -147,6 +147,25 @@ func (adapter *PgsqlAdapter) AddKey(name string, table string, column string, ke
return "", errors.New("not implemented") return "", errors.New("not implemented")
} }
// TODO: Implement this
// TODO: Test to make sure everything works here
func (adapter *PgsqlAdapter) AddForeignKey(name string, table string, column string, ftable string, fcolumn string, cascade bool) (out string, e error) {
var c = func(str string, val bool) {
if e != nil || !val {
return
}
e = errors.New("You need a "+str+" for this table")
}
c("name",table=="")
c("column",column=="")
c("ftable",ftable=="")
c("fcolumn",fcolumn=="")
if e != nil {
return "", e
}
return "", errors.New("not implemented")
}
// TODO: Test this // 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 // ! 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 (adapter *PgsqlAdapter) SimpleInsert(name string, table string, columns string, fields string) (string, error) { func (adapter *PgsqlAdapter) SimpleInsert(name string, table string, columns string, fields string) (string, error) {

View File

@ -23,6 +23,10 @@ type DBTableColumn struct {
type DBTableKey struct { type DBTableKey struct {
Columns string Columns string
Type string Type string
// Foreign keys only
FTable string
Cascade bool
} }
type DBSelect struct { type DBSelect struct {
@ -111,6 +115,7 @@ type Adapter interface {
AddColumn(name string, table string, column DBTableColumn, key *DBTableKey) (string, error) AddColumn(name string, table string, column DBTableColumn, key *DBTableKey) (string, error)
AddIndex(name string, table string, iname string, colname string) (string, error) AddIndex(name string, table string, iname string, colname string) (string, error)
AddKey(name string, table string, column string, key DBTableKey) (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) SimpleInsert(name string, table string, columns string, fields string) (string, error)
SimpleUpdate(up *updatePrebuilder) (string, error) SimpleUpdate(up *updatePrebuilder) (string, error)
SimpleUpdateSelect(up *updatePrebuilder) (string, error) // ! Experimental SimpleUpdateSelect(up *updatePrebuilder) (string, error) // ! Experimental

View File

@ -10,6 +10,7 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"io"
"errors" "errors"
"log" "log"
"net/http" "net/http"
@ -27,7 +28,7 @@ var successJSONBytes = []byte(`{"success":"1"}`)
// TODO: Refactor this // TODO: Refactor this
// TODO: Use the phrase system // TODO: Use the phrase system
var phraseLoginAlerts = []byte(`{"msgs":[{"msg":"Login to see your alerts","path":"/accounts/login"}],"msgCount":0}`) var phraseLoginAlerts = []byte(`{"msgs":[{"msg":"Login to see your alerts","path":"/accounts/login"}],"count":0}`)
// TODO: Refactor this endpoint // TODO: Refactor this endpoint
// TODO: Move this into the routes package // TODO: Move this into the routes package
@ -40,6 +41,9 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError
} }
action := r.FormValue("action") action := r.FormValue("action")
if action == "" {
action = "get"
}
if action != "get" && action != "set" { if action != "get" && action != "set" {
return c.PreErrorJS("Invalid Action", w, r) return c.PreErrorJS("Invalid Action", w, r)
} }
@ -86,8 +90,8 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError
} }
var msglist string var msglist string
var msgCount int var count int
err = stmts.getActivityCountByWatcher.QueryRow(user.ID).Scan(&msgCount) err = stmts.getActivityCountByWatcher.QueryRow(user.ID).Scan(&count)
if err == ErrNoRows { if err == ErrNoRows {
return c.PreErrorJS("Couldn't find the parent topic", w, r) return c.PreErrorJS("Couldn't find the parent topic", w, r)
} else if err != nil { } else if err != nil {
@ -141,7 +145,7 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError
if len(msglist) != 0 { if len(msglist) != 0 {
msglist = msglist[0 : len(msglist)-1] msglist = msglist[0 : len(msglist)-1]
} }
_, _ = w.Write([]byte(`{"msgs":[` + msglist + `],"msgCount":` + strconv.Itoa(msgCount) + `}`)) _, _ = io.WriteString(w, `{"msgs":[` + msglist + `],"count":` + strconv.Itoa(count) + `}`)
default: default:
return c.PreErrorJS("Invalid Module", w, r) return c.PreErrorJS("Invalid Module", w, r)
} }
@ -296,9 +300,9 @@ func routeJSAntispam(w http.ResponseWriter, r *http.Request, user c.User) c.Rout
jsToken := hex.EncodeToString(h.Sum(nil)) jsToken := hex.EncodeToString(h.Sum(nil))
var innerCode = "`document.getElementByld('golden-watch').value = '" + jsToken + "';`" var innerCode = "`document.getElementByld('golden-watch').value = '" + jsToken + "';`"
w.Write([]byte(`let hihi = ` + innerCode + `; io.WriteString(w, `let hihi = ` + innerCode + `;
hihi = hihi.replace('ld','Id'); hihi = hihi.replace('ld','Id');
eval(hihi);`)) eval(hihi);`)
return nil return nil
} }

View File

@ -452,8 +452,7 @@ func CreateTopic(w http.ResponseWriter, r *http.Request, user c.User, header *c.
} }
} }
ctpage := c.CreateTopicPage{header, forumList, fid} return renderTemplate("create_topic", w, r, header, c.CreateTopicPage{header, forumList, fid})
return renderTemplate("create_topic", w, r, header, ctpage)
} }
func CreateTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError { func CreateTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
@ -461,7 +460,6 @@ func CreateTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Ro
if err != nil { if err != nil {
return c.LocalError("The provided ForumID is not a valid number.", w, r, user) return c.LocalError("The provided ForumID is not a valid number.", w, r, user)
} }
// TODO: Add hooks to make use of headerLite // TODO: Add hooks to make use of headerLite
lite, ferr := c.SimpleForumUserCheck(w, r, &user, fid) lite, ferr := c.SimpleForumUserCheck(w, r, &user, fid)
if ferr != nil { if ferr != nil {

View File

@ -1,4 +1,5 @@
CREATE TABLE [activity_stream_matches] ( CREATE TABLE [activity_stream_matches] (
[watcher] int not null, [watcher] int not null,
[asid] int not null [asid] int not null,
foreign key([asid],[asid])
); );

View File

@ -1,4 +1,5 @@
CREATE TABLE `activity_stream_matches` ( CREATE TABLE `activity_stream_matches` (
`watcher` int not null, `watcher` int not null,
`asid` int not null `asid` int not null,
foreign key(`asid`) REFERENCES `activity_stream`(`asid`) ON DELETE CASCADE
); );

View File

@ -1,4 +1,5 @@
CREATE TABLE "activity_stream_matches" ( CREATE TABLE "activity_stream_matches" (
`watcher` int not null, `watcher` int not null,
`asid` int not null `asid` int not null,
foreign key(`asid`,`asid`)
); );

View File

@ -7,7 +7,7 @@
{{range .Header.PreScriptsAsync}} {{range .Header.PreScriptsAsync}}
<script async type="text/javascript" src="/static/{{.}}"></script>{{end}} <script async type="text/javascript" src="/static/{{.}}"></script>{{end}}
<meta property="x-loggedin" content="{{.CurrentUser.Loggedin}}" /> <meta property="x-loggedin" content="{{.CurrentUser.Loggedin}}" />
<script type="text/javascript" src="/static/init.js?i=5"></script> <script type="text/javascript" src="/static/init.js?i=6"></script>
{{range .Header.ScriptsAsync}} {{range .Header.ScriptsAsync}}
<script async type="text/javascript" src="/static/{{.}}"></script>{{end}} <script async type="text/javascript" src="/static/{{.}}"></script>{{end}}
<script type="text/javascript" src="/static/jquery-3.1.1.min.js"></script> <script type="text/javascript" src="/static/jquery-3.1.1.min.js"></script>

View File

@ -5,8 +5,10 @@ import (
"log" "log"
"sync/atomic" "sync/atomic"
"time" "time"
"database/sql"
c "github.com/Azareal/Gosora/common" c "github.com/Azareal/Gosora/common"
"github.com/Azareal/Gosora/query_gen"
) )
// 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? // 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?
@ -55,6 +57,7 @@ func tickLoop(thumbChan chan bool) {
secondTicker := time.NewTicker(time.Second) secondTicker := time.NewTicker(time.Second)
fifteenMinuteTicker := time.NewTicker(15 * time.Minute) fifteenMinuteTicker := time.NewTicker(15 * time.Minute)
hourTicker := time.NewTicker(time.Hour) hourTicker := time.NewTicker(time.Hour)
dailyTicker := time.NewTicker(time.Hour * 24)
for { for {
select { select {
case <-halfSecondTicker.C: case <-halfSecondTicker.C:
@ -122,6 +125,23 @@ func tickLoop(thumbChan chan bool) {
runTasks(c.ScheduledHourTasks) runTasks(c.ScheduledHourTasks)
runHook("after_hour_tick") runHook("after_hour_tick")
// TODO: Handle the instance going down a lot better
case <-dailyTicker.C:
// TODO: Find a more efficient way of doing this
err := qgen.NewAcc().Select("activity_stream").Cols("asid").EachInt(func(asid int) error {
count, err := qgen.NewAcc().Count("activity_stream_matches").Where("asid = ?").Total()
if err != sql.ErrNoRows {
return err
}
if count > 0 {
return nil
}
_, err = qgen.NewAcc().Delete("activity_stream").Where("asid = ?").Run(asid)
return err
})
if err != nil && err != sql.ErrNoRows {
c.LogError(err)
}
} }
// TODO: Handle the daily clean-up. // TODO: Handle the daily clean-up.