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 {
if name == "CreateTableStart" {
var found string
var table = args[0].(*qgen.DB_Install_Table)
var table = args[0].(*qgen.DBInstallTable)
for _, key := range table.Keys {
if key.Type == "primary" {
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
},
[]tblKey{
tblKey{"uid", "primary"},
tblKey{"name", "unique"},
tblKey{"uid", "primary","",false},
tblKey{"name", "unique","",false},
},
)
@ -64,7 +64,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"tag", "varchar", 50, false, false, "''"},
},
[]tblKey{
tblKey{"gid", "primary"},
tblKey{"gid", "primary","",false},
},
)
@ -83,7 +83,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"createdAt", "createdAt", 0, false, false, ""},
},
[]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
},
[]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
},
[]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"},
},
[]tblKey{
tblKey{"fid", "primary"},
tblKey{"fid", "primary","",false},
},
)
@ -204,7 +204,7 @@ func createTables(adapter qgen.Adapter) error {
},
[]tblKey{
// 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, "''"},
},
[]tblKey{
tblKey{"tid", "primary"},
tblKey{"content", "fulltext"},
tblKey{"tid", "primary","",false},
tblKey{"content", "fulltext","",false},
},
)
@ -264,8 +264,8 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"poll", "int", 0, false, false, "0"},
},
[]tblKey{
tblKey{"rid", "primary"},
tblKey{"content", "fulltext"},
tblKey{"rid", "primary","",false},
tblKey{"content", "fulltext","",false},
},
)
@ -281,7 +281,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"extra", "varchar", 200, false, false, ""},
},
[]tblKey{
tblKey{"attachID", "primary"},
tblKey{"attachID", "primary","",false},
},
)
@ -295,7 +295,7 @@ func createTables(adapter qgen.Adapter) error {
// TODO: Add a createdBy column?
},
[]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"},
},
[]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"},
},
[]tblKey{
tblKey{"rid", "primary"},
tblKey{"rid", "primary","",false},
},
)
@ -363,7 +363,10 @@ func createTables(adapter qgen.Adapter) error {
[]tblColumn{
tblColumn{"watcher", "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", "", "",
@ -376,7 +379,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"elementID", "int", 0, false, false, ""}, /* the ID of the element being acted upon */
},
[]tblKey{
tblKey{"asid", "primary"},
tblKey{"asid", "primary","",false},
},
)
@ -398,7 +401,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"constraints", "varchar", 200, false, false, "''"},
},
[]tblKey{
tblKey{"name", "unique"},
tblKey{"name", "unique","",false},
},
)
@ -409,7 +412,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"replacement", "varchar", 200, false, false, ""},
},
[]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"},
},
[]tblKey{
tblKey{"uname", "unique"},
tblKey{"uname", "unique","",false},
},
)
@ -431,7 +434,7 @@ func createTables(adapter qgen.Adapter) error {
//tblColumn{"profileUserVars", "text", 0, false, false, "''"},
},
[]tblKey{
tblKey{"uname", "unique"},
tblKey{"uname", "unique","",false},
},
)
@ -446,7 +449,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"data", "text", 0, false, false, "''"},
},
[]tblKey{
tblKey{"wid", "primary"},
tblKey{"wid", "primary","",false},
},
)
@ -455,7 +458,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"mid", "int", 0, false, true, ""},
},
[]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"},
},
[]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
},
[]tblKey{
tblKey{"pid", "primary"},
tblKey{"pid", "primary","",false},
},
)
@ -510,7 +513,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"doneAt", "createdAt", 0, false, false, ""},
},
[]tblKey{
tblKey{"rlid", "primary"},
tblKey{"rlid", "primary","",false},
},
)
@ -523,7 +526,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"doneAt", "createdAt", 0, false, false, ""},
},
[]tblKey{
tblKey{"lid", "primary"},
tblKey{"lid", "primary","",false},
},
)

View File

@ -180,6 +180,8 @@ type TopicStmts struct {
createLike *sql.Stmt
addLikesToTopic *sql.Stmt
delete *sql.Stmt
deleteActivity *sql.Stmt
deleteActivitySubs *sql.Stmt
edit *sql.Stmt
setPoll *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(),
addLikesToTopic: acc.Update("topics").Set("likeCount = likeCount + ?").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?
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(),
@ -325,6 +329,14 @@ func (topic *Topic) Delete() error {
_, err = topicStmts.delete.Exec(topic.ID)
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
}

View File

@ -10,10 +10,12 @@ import (
"bytes"
"database/sql"
"fmt"
"os"
"io/ioutil"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/Azareal/Gosora/query_gen"
_ "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("Switching to database ", ins.dbName)
/*fmt.Println("Switching to database ", 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 {
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
ins.db = db
qgen.Builder.SetConn(db)
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) {
fmt.Println("Creating the tables")
files, _ := ioutil.ReadDir("./schema/mysql/")
for _, f := range files {
files, err := ioutil.ReadDir("./schema/mysql/")
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_") {
continue
}
var table, ext string
table = strings.TrimPrefix(f.Name(), "query_")
ext = filepath.Ext(table)
table := strings.TrimPrefix(f.Name(), "query_")
ext := filepath.Ext(table)
if ext != ".sql" {
continue
}
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
_, err = ins.db.Exec("DROP TABLE IF EXISTS `" + table + "`;")
if err != nil {
fmt.Println("Failed query:", "DROP TABLE IF EXISTS `"+table+"`;")
return err
for _, tbl := range after {
if tbl == table {
a = append(a, f)
continue Outer
}
}
fmt.Printf("Creating table '%s'\n", table)
data, err := ioutil.ReadFile("./schema/mysql/" + f.Name())
if err != nil {
return err
if i%2 == 0 {
c1 <- f
} else {
c2 <- f
}
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 {
fmt.Println("Failed query:", string(data))
return err
}
}
return nil
}

View File

@ -32,6 +32,7 @@ func init() {
addPatch(17, patch17)
addPatch(18, patch18)
addPatch(19, patch19)
addPatch(20, patch20)
}
func patch0(scanner *bufio.Scanner) (err error) {
@ -49,7 +50,7 @@ func patch0(scanner *bufio.Scanner) (err error) {
tblColumn{"mid", "int", 0, false, true, ""},
},
[]tblKey{
tblKey{"mid", "primary"},
tblKey{"mid", "primary","",false},
},
))
if err != nil {
@ -76,7 +77,7 @@ func patch0(scanner *bufio.Scanner) (err error) {
tblColumn{"adminOnly", "boolean", 0, false, false, "0"},
},
[]tblKey{
tblKey{"miid", "primary"},
tblKey{"miid", "primary","",false},
},
))
if err != nil {
@ -183,7 +184,7 @@ func patch3(scanner *bufio.Scanner) error {
tblColumn{"doneAt", "createdAt", 0, false, false, ""},
},
[]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"},
},
[]tblKey{
tblKey{"pid", "primary"},
tblKey{"pid", "primary","",false},
},
))
if err != nil {
@ -289,7 +290,7 @@ func patch5(scanner *bufio.Scanner) error {
tblColumn{"createdAt", "createdAt", 0, false, false, ""},
},
[]tblKey{
tblKey{"uid", "primary"},
tblKey{"uid", "primary","",false},
},
))
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
},
[]tblKey{
tblKey{"uid", "primary"},
tblKey{"uid", "primary","",false},
},
))
}
@ -391,7 +392,7 @@ func patch9(scanner *bufio.Scanner) error {
tblColumn{"doneAt", "createdAt", 0, false, false, ""},
},
[]tblKey{
tblKey{"lid", "primary"},
tblKey{"lid", "primary","",false},
},
))
}
@ -514,7 +515,7 @@ func patch12(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 {
return err
}
@ -523,15 +524,15 @@ func patch13(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 {
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 {
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 {
return err
}
@ -603,3 +604,26 @@ func patch19(scanner *bufio.Scanner) error {
}, 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({
type: 'get',
dataType: 'json',
url:'/api/?action=get&module=alerts',
url:'/api/?module=alerts',
success: (data) => {
if("errmsg" in data) {
setAlertError(menuAlerts,data.errmsg)
@ -147,8 +147,8 @@ function loadAlerts(menuAlerts) {
alertList = [];
alertMapping = {};
for(var i in data.msgs) addAlert(data.msgs[i]);
console.log("data.msgCount:",data.msgCount)
alertCount = data.msgCount;
console.log("data.count:",data.count)
alertCount = data.count;
updateAlertList(menuAlerts)
},
error: (magic,theStatus,error) => {

View File

@ -178,7 +178,7 @@ function initPhrases(loggedIn, panel = false) {
console.log("in initPhrases")
console.log("tmlInits:",tmplInits)
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 e = ",status,topic_list";
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))
}
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) {
return build.prepare(build.adapter.SimpleInsert("", table, columns, fields))
}

View File

@ -3,17 +3,17 @@ package qgen
var Install *installer
func init() {
Install = &installer{instructions: []DB_Install_Instruction{}}
Install = &installer{instructions: []DBInstallInstruction{}}
}
type DB_Install_Instruction struct {
type DBInstallInstruction struct {
Table string
Contents string
Type string
}
// TODO: Add methods to this to construct it OO-like
type DB_Install_Table struct {
type DBInstallTable struct {
Name string
Charset 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
type installer struct {
adapter Adapter
instructions []DB_Install_Instruction
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
instructions []DBInstallInstruction
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
}
@ -41,7 +41,7 @@ func (install *installer) SetAdapter(name string) error {
func (install *installer) SetAdapterInstance(adapter Adapter) {
install.adapter = adapter
install.instructions = []DB_Install_Instruction{}
install.instructions = []DBInstallInstruction{}
}
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 {
tableStruct := &DB_Install_Table{table, charset, collation, columns, keys}
tableStruct := &DBInstallTable{table, charset, collation, columns, keys}
err := install.RunHook("CreateTableStart", tableStruct)
if err != nil {
return err
@ -62,7 +62,7 @@ func (install *installer) CreateTable(table string, charset string, collation st
if err != nil {
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)
return nil
}
@ -81,7 +81,7 @@ func (install *installer) AddIndex(table string, iname string, colname string) e
if err != nil {
return err
}
install.instructions = append(install.instructions, DB_Install_Instruction{table, res, "index"})
install.instructions = append(install.instructions, DBInstallInstruction{table, res, "index"})
return nil
}
@ -99,7 +99,7 @@ func (install *installer) SimpleInsert(table string, columns string, fields stri
if err != nil {
return err
}
install.instructions = append(install.instructions, DB_Install_Instruction{table, res, "insert"})
install.instructions = append(install.instructions, DBInstallInstruction{table, res, "insert"})
return nil
}

View File

@ -53,6 +53,7 @@ func (adapter *MssqlAdapter) DropTable(name string, table string) (string, error
return querystr, nil
}
// 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 (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")
}
// 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) {
if 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" {
querystr += " key"
}
querystr += "("
for _, column := range strings.Split(key.Columns, ",") {
querystr += "`" + column + "`,"
if key.Type == "foreign" {
cols := strings.Split(key.Columns, ",")
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: 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 == "" {
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
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
adapter.pushStatement(name, "add-column", querystr)
a.pushStatement(name, "add-column", querystr)
return querystr, nil
}
// 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 == "" {
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 + "`);"
// 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
}
// TODO: Test to make sure everything works here
// 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 == "" {
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")
}
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
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
}

View File

@ -147,6 +147,25 @@ func (adapter *PgsqlAdapter) AddKey(name string, table string, column string, ke
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
// ! 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) {

View File

@ -23,6 +23,10 @@ type DBTableColumn struct {
type DBTableKey struct {
Columns string
Type string
// Foreign keys only
FTable string
Cascade bool
}
type DBSelect struct {
@ -111,6 +115,7 @@ type Adapter interface {
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)
SimpleUpdate(up *updatePrebuilder) (string, error)
SimpleUpdateSelect(up *updatePrebuilder) (string, error) // ! Experimental

View File

@ -10,6 +10,7 @@ import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"errors"
"log"
"net/http"
@ -27,7 +28,7 @@ var successJSONBytes = []byte(`{"success":"1"}`)
// TODO: Refactor this
// 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: 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")
if action == "" {
action = "get"
}
if action != "get" && action != "set" {
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 msgCount int
err = stmts.getActivityCountByWatcher.QueryRow(user.ID).Scan(&msgCount)
var count int
err = stmts.getActivityCountByWatcher.QueryRow(user.ID).Scan(&count)
if err == ErrNoRows {
return c.PreErrorJS("Couldn't find the parent topic", w, r)
} 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 {
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:
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))
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');
eval(hihi);`))
eval(hihi);`)
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, ctpage)
return renderTemplate("create_topic", w, r, header, c.CreateTopicPage{header, forumList, fid})
}
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 {
return c.LocalError("The provided ForumID is not a valid number.", w, r, user)
}
// TODO: Add hooks to make use of headerLite
lite, ferr := c.SimpleForumUserCheck(w, r, &user, fid)
if ferr != nil {

View File

@ -1,4 +1,5 @@
CREATE TABLE [activity_stream_matches] (
[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` (
`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" (
`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}}
<script async type="text/javascript" src="/static/{{.}}"></script>{{end}}
<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}}
<script async type="text/javascript" src="/static/{{.}}"></script>{{end}}
<script type="text/javascript" src="/static/jquery-3.1.1.min.js"></script>

View File

@ -5,8 +5,10 @@ import (
"log"
"sync/atomic"
"time"
"database/sql"
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?
@ -55,6 +57,7 @@ func tickLoop(thumbChan chan bool) {
secondTicker := time.NewTicker(time.Second)
fifteenMinuteTicker := time.NewTicker(15 * time.Minute)
hourTicker := time.NewTicker(time.Hour)
dailyTicker := time.NewTicker(time.Hour * 24)
for {
select {
case <-halfSecondTicker.C:
@ -122,6 +125,23 @@ func tickLoop(thumbChan chan bool) {
runTasks(c.ScheduledHourTasks)
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.