diff --git a/cmd/query_gen/spitter.go b/cmd/query_gen/spitter.go index 9176e710..5ddd4f01 100644 --- a/cmd/query_gen/spitter.go +++ b/cmd/query_gen/spitter.go @@ -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, ",") diff --git a/cmd/query_gen/tables.go b/cmd/query_gen/tables.go index 69fda816..abfaa8f1 100644 --- a/cmd/query_gen/tables.go +++ b/cmd/query_gen/tables.go @@ -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}, }, ) diff --git a/common/topic.go b/common/topic.go index ae5cac61..abfab29c 100644 --- a/common/topic.go +++ b/common/topic.go @@ -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 } diff --git a/install/mysql.go b/install/mysql.go index 6089a51a..f4089792 100644 --- a/install/mysql.go +++ b/install/mysql.go @@ -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) + + var first error + for err := range e { + if first == nil { + first = err + } + } + if first != nil { + return first + } - _, err = ins.db.Exec(string(data)) + 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 } diff --git a/patcher/patches.go b/patcher/patches.go index ec4a92a9..90f07a20 100644 --- a/patcher/patches.go +++ b/patcher/patches.go @@ -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 } @@ -602,4 +603,27 @@ func patch19(scanner *bufio.Scanner) error { tblColumn{"createdAt", "datetime", 0, false, false, ""}, }, nil, )) -} \ No newline at end of file +} + +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)) +} diff --git a/public/global.js b/public/global.js index ed07f3a4..dad38319 100644 --- a/public/global.js +++ b/public/global.js @@ -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) => { diff --git a/public/init.js b/public/init.js index 544b46f1..13c8e155 100644 --- a/public/init.js +++ b/public/init.js @@ -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? diff --git a/query_gen/builder.go b/query_gen/builder.go index 1e8de785..a95938e0 100644 --- a/query_gen/builder.go +++ b/query_gen/builder.go @@ -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)) } diff --git a/query_gen/install.go b/query_gen/install.go index 797fa863..3e8a743d 100644 --- a/query_gen/install.go +++ b/query_gen/install.go @@ -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 } diff --git a/query_gen/mssql.go b/query_gen/mssql.go index e9d76b55..12438c2b 100644 --- a/query_gen/mssql.go +++ b/query_gen/mssql.go @@ -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") diff --git a/query_gen/mysql.go b/query_gen/mysql.go index 524258a2..d56d2a4b 100644 --- a/query_gen/mysql.go +++ b/query_gen/mysql.go @@ -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 } diff --git a/query_gen/pgsql.go b/query_gen/pgsql.go index f668be03..54a87aaf 100644 --- a/query_gen/pgsql.go +++ b/query_gen/pgsql.go @@ -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) { diff --git a/query_gen/querygen.go b/query_gen/querygen.go index 5d9483ea..f0c50ec0 100644 --- a/query_gen/querygen.go +++ b/query_gen/querygen.go @@ -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 diff --git a/routes.go b/routes.go index fb5078df..a6a47f83 100644 --- a/routes.go +++ b/routes.go @@ -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 } diff --git a/routes/topic.go b/routes/topic.go index 9f3ad840..b71abeaf 100644 --- a/routes/topic.go +++ b/routes/topic.go @@ -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 { diff --git a/schema/mssql/query_activity_stream_matches.sql b/schema/mssql/query_activity_stream_matches.sql index e6e9cda6..f608225a 100644 --- a/schema/mssql/query_activity_stream_matches.sql +++ b/schema/mssql/query_activity_stream_matches.sql @@ -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]) ); \ No newline at end of file diff --git a/schema/mysql/query_activity_stream_matches.sql b/schema/mysql/query_activity_stream_matches.sql index d4139a9b..661128c0 100644 --- a/schema/mysql/query_activity_stream_matches.sql +++ b/schema/mysql/query_activity_stream_matches.sql @@ -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 ); \ No newline at end of file diff --git a/schema/pgsql/query_activity_stream_matches.sql b/schema/pgsql/query_activity_stream_matches.sql index 3efddb73..a1c5ed9b 100644 --- a/schema/pgsql/query_activity_stream_matches.sql +++ b/schema/pgsql/query_activity_stream_matches.sql @@ -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`) ); \ No newline at end of file diff --git a/templates/header.html b/templates/header.html index fe999d9e..8ec58175 100644 --- a/templates/header.html +++ b/templates/header.html @@ -7,7 +7,7 @@ {{range .Header.PreScriptsAsync}} {{end}} - + {{range .Header.ScriptsAsync}} {{end}} diff --git a/tickloop.go b/tickloop.go index 599bc8e9..cfc05a09 100644 --- a/tickloop.go +++ b/tickloop.go @@ -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.