Refactored the installer's code. We're moving towards supporting multiple database engines.

More progress has been made with PostgreSQL, but it's still incomplete and experimental.
The items on the Social Group menu bar are no longer decorative and now let you jump from page to page.
Social Group permissions are now fully functional. More features to come like privacy levels, secondary board permissions, etc. for Social Groups.

One of our dependencies added a dependecy, so we've added that dependency as a dependency.
Tweaked the ForumStore to better conform to the tests.
Fixed an issue with the generated file comment stopping the build tags from being read by the compiler for a few files.
Fixed the test system.
Renamed the route_create_topic handler to route_topic_create_submit
Moved the user table into the query generator.
Fixed a bug in MySQL where it doesn't allow unique keys longer than 180 characters.
Fixed a race condition in the forum deletion handler.
Fixed a crash bug where Gosora crashes when [rand]0[/rand] is passed to the BBCode parser.
Fixed a bug with superadmins not being able to see all the forums they should be able to.
Fixed a bug in simple_forum_session_check where it assumes every request has an error.

Tests:
Added 8 ForumStore tests.
Added 4 Auth tests.
Added 14 bbcode_full_parse tests.
Added 4 bbcode_regex_parse tests.
Fixed a bug in one of the bbcode_full_parse tests.

Apparently, routes.go wasn't commited in the previous commit o_o
This commit is contained in:
Azareal 2017-07-12 12:05:18 +01:00
parent dd8f68b6d2
commit e30707bc17
37 changed files with 1704 additions and 302 deletions

View File

@ -165,6 +165,8 @@ We're looking for ways to clean-up the plugin system so that all of them (except
* github.com/shirou/gopsutil For pulling information on CPU and memory usage.
* github.com/StackExchange/wmi Dependency for gopsutil on Windows.
* golang.org/x/sys/windows Also a dependency for gopsutil on Windows.
* github.com/gorilla/websocket Needed for Gosora's Optional WebSockets Module.

View File

@ -12,6 +12,7 @@ import "golang.org/x/crypto/bcrypt"
var auth Auth
var ErrMismatchedHashAndPassword = bcrypt.ErrMismatchedHashAndPassword
var ErrPasswordTooLong = errors.New("The password you selected is too long") // Silly, but we don't want bcrypt to bork on us
type Auth interface
{

View File

@ -2,7 +2,6 @@
package main
import "log"
import "errors"
import "sync"
//import "sync/atomic"
import "database/sql"
@ -13,8 +12,6 @@ var forum_create_mutex sync.Mutex
var forum_perms map[int]map[int]ForumPerms // [gid][fid]Perms
var fstore ForumStore
var err_noforum = errors.New("This forum doesn't exist")
type ForumStore interface
{
LoadForums() error
@ -122,21 +119,21 @@ func (sfs *StaticForumStore) DirtyGet(id int) *Forum {
func (sfs *StaticForumStore) Get(id int) (*Forum, error) {
if !((id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name!="") {
return nil, err_noforum
return nil, ErrNoRows
}
return sfs.forums[id], nil
}
func (sfs *StaticForumStore) CascadeGet(id int) (*Forum, error) {
if !((id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name != "") {
return nil, err_noforum
return nil, ErrNoRows
}
return sfs.forums[id], nil
}
func (sfs *StaticForumStore) CascadeGetCopy(id int) (forum Forum, err error) {
if !((id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name != "") {
return forum, err_noforum
return forum, ErrNoRows
}
return *sfs.forums[id], nil
}

View File

@ -1,6 +1,7 @@
// Code generated by Gosora. More below:
/* This file was generated by Gosora's Query Generator. Please try to avoid modifying this file, as it might change at any time. */
// +build !pgsql !sqlite !mssql
/* This file was generated by Gosora's Query Generator. Please try to avoid modifying this file, as it might change at any time. */
package main
import "log"
@ -112,7 +113,7 @@ var add_forum_perms_to_forum_staff_stmt *sql.Stmt
var add_forum_perms_to_forum_members_stmt *sql.Stmt
var notify_watchers_stmt *sql.Stmt
func gen_mysql() (err error) {
func _gen_mysql() (err error) {
if debug_mode {
log.Print("Building the generated statements")
}

275
gen_pgsql.go Normal file
View File

@ -0,0 +1,275 @@
// +build pgsql
// This file was generated by Gosora's Query Generator. Please try to avoid modifying this file, as it might change at any time.
package main
import "log"
import "database/sql"
var add_replies_to_topic_stmt *sql.Stmt
var remove_replies_from_topic_stmt *sql.Stmt
var add_topics_to_forum_stmt *sql.Stmt
var remove_topics_from_forum_stmt *sql.Stmt
var update_forum_cache_stmt *sql.Stmt
var add_likes_to_topic_stmt *sql.Stmt
var add_likes_to_reply_stmt *sql.Stmt
var edit_topic_stmt *sql.Stmt
var edit_reply_stmt *sql.Stmt
var stick_topic_stmt *sql.Stmt
var unstick_topic_stmt *sql.Stmt
var update_last_ip_stmt *sql.Stmt
var update_session_stmt *sql.Stmt
var set_password_stmt *sql.Stmt
var set_avatar_stmt *sql.Stmt
var set_username_stmt *sql.Stmt
var change_group_stmt *sql.Stmt
var activate_user_stmt *sql.Stmt
var update_user_level_stmt *sql.Stmt
var increment_user_score_stmt *sql.Stmt
var increment_user_posts_stmt *sql.Stmt
var increment_user_bigposts_stmt *sql.Stmt
var increment_user_megaposts_stmt *sql.Stmt
var increment_user_topics_stmt *sql.Stmt
var edit_profile_reply_stmt *sql.Stmt
var delete_forum_stmt *sql.Stmt
var update_forum_stmt *sql.Stmt
var update_setting_stmt *sql.Stmt
var update_plugin_stmt *sql.Stmt
var update_plugin_install_stmt *sql.Stmt
var update_theme_stmt *sql.Stmt
var update_user_stmt *sql.Stmt
var update_group_perms_stmt *sql.Stmt
var update_group_rank_stmt *sql.Stmt
var update_group_stmt *sql.Stmt
var update_email_stmt *sql.Stmt
var verify_email_stmt *sql.Stmt
func _gen_pgsql() (err error) {
if debug_mode {
log.Print("Building the generated statements")
}
log.Print("Preparing add_replies_to_topic statement.")
add_replies_to_topic_stmt, err = db.Prepare("UPDATE `topics` SET `postCount` = `postCount` + ?,`lastReplyAt` = NOW() WHERE `tid` = ?")
if err != nil {
return err
}
log.Print("Preparing remove_replies_from_topic statement.")
remove_replies_from_topic_stmt, err = db.Prepare("UPDATE `topics` SET `postCount` = `postCount` - ? WHERE `tid` = ?")
if err != nil {
return err
}
log.Print("Preparing add_topics_to_forum statement.")
add_topics_to_forum_stmt, err = db.Prepare("UPDATE `forums` SET `topicCount` = `topicCount` + ? WHERE `fid` = ?")
if err != nil {
return err
}
log.Print("Preparing remove_topics_from_forum statement.")
remove_topics_from_forum_stmt, err = db.Prepare("UPDATE `forums` SET `topicCount` = `topicCount` - ? WHERE `fid` = ?")
if err != nil {
return err
}
log.Print("Preparing update_forum_cache statement.")
update_forum_cache_stmt, err = db.Prepare("UPDATE `forums` SET `lastTopic` = ?,`lastTopicID` = ?,`lastReplyer` = ?,`lastReplyerID` = ?,`lastTopicTime` = NOW() WHERE `fid` = ?")
if err != nil {
return err
}
log.Print("Preparing add_likes_to_topic statement.")
add_likes_to_topic_stmt, err = db.Prepare("UPDATE `topics` SET `likeCount` = `likeCount` + ? WHERE `tid` = ?")
if err != nil {
return err
}
log.Print("Preparing add_likes_to_reply statement.")
add_likes_to_reply_stmt, err = db.Prepare("UPDATE `replies` SET `likeCount` = `likeCount` + ? WHERE `rid` = ?")
if err != nil {
return err
}
log.Print("Preparing edit_topic statement.")
edit_topic_stmt, err = db.Prepare("UPDATE `topics` SET `title` = ?,`content` = ?,`parsed_content` = ?,`is_closed` = ? WHERE `tid` = ?")
if err != nil {
return err
}
log.Print("Preparing edit_reply statement.")
edit_reply_stmt, err = db.Prepare("UPDATE `replies` SET `content` = ?,`parsed_content` = ? WHERE `rid` = ?")
if err != nil {
return err
}
log.Print("Preparing stick_topic statement.")
stick_topic_stmt, err = db.Prepare("UPDATE `topics` SET `sticky` = 1 WHERE `tid` = ?")
if err != nil {
return err
}
log.Print("Preparing unstick_topic statement.")
unstick_topic_stmt, err = db.Prepare("UPDATE `topics` SET `sticky` = 0 WHERE `tid` = ?")
if err != nil {
return err
}
log.Print("Preparing update_last_ip statement.")
update_last_ip_stmt, err = db.Prepare("UPDATE `users` SET `last_ip` = ? WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing update_session statement.")
update_session_stmt, err = db.Prepare("UPDATE `users` SET `session` = ? WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing set_password statement.")
set_password_stmt, err = db.Prepare("UPDATE `users` SET `password` = ?,`salt` = ? WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing set_avatar statement.")
set_avatar_stmt, err = db.Prepare("UPDATE `users` SET `avatar` = ? WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing set_username statement.")
set_username_stmt, err = db.Prepare("UPDATE `users` SET `name` = ? WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing change_group statement.")
change_group_stmt, err = db.Prepare("UPDATE `users` SET `group` = ? WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing activate_user statement.")
activate_user_stmt, err = db.Prepare("UPDATE `users` SET `active` = 1 WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing update_user_level statement.")
update_user_level_stmt, err = db.Prepare("UPDATE `users` SET `level` = ? WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing increment_user_score statement.")
increment_user_score_stmt, err = db.Prepare("UPDATE `users` SET `score` = `score` + ? WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing increment_user_posts statement.")
increment_user_posts_stmt, err = db.Prepare("UPDATE `users` SET `posts` = `posts` + ? WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing increment_user_bigposts statement.")
increment_user_bigposts_stmt, err = db.Prepare("UPDATE `users` SET `posts` = `posts` + ?,`bigposts` = `bigposts` + ? WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing increment_user_megaposts statement.")
increment_user_megaposts_stmt, err = db.Prepare("UPDATE `users` SET `posts` = `posts` + ?,`bigposts` = `bigposts` + ?,`megaposts` = `megaposts` + ? WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing increment_user_topics statement.")
increment_user_topics_stmt, err = db.Prepare("UPDATE `users` SET `topics` = `topics` + ? WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing edit_profile_reply statement.")
edit_profile_reply_stmt, err = db.Prepare("UPDATE `users_replies` SET `content` = ?,`parsed_content` = ? WHERE `rid` = ?")
if err != nil {
return err
}
log.Print("Preparing delete_forum statement.")
delete_forum_stmt, err = db.Prepare("UPDATE `forums` SET `name` = '',`active` = 0 WHERE `fid` = ?")
if err != nil {
return err
}
log.Print("Preparing update_forum statement.")
update_forum_stmt, err = db.Prepare("UPDATE `forums` SET `name` = ?,`desc` = ?,`active` = ?,`preset` = ? WHERE `fid` = ?")
if err != nil {
return err
}
log.Print("Preparing update_setting statement.")
update_setting_stmt, err = db.Prepare("UPDATE `settings` SET `content` = ? WHERE `name` = ?")
if err != nil {
return err
}
log.Print("Preparing update_plugin statement.")
update_plugin_stmt, err = db.Prepare("UPDATE `plugins` SET `active` = ? WHERE `uname` = ?")
if err != nil {
return err
}
log.Print("Preparing update_plugin_install statement.")
update_plugin_install_stmt, err = db.Prepare("UPDATE `plugins` SET `installed` = ? WHERE `uname` = ?")
if err != nil {
return err
}
log.Print("Preparing update_theme statement.")
update_theme_stmt, err = db.Prepare("UPDATE `themes` SET `default` = ? WHERE `uname` = ?")
if err != nil {
return err
}
log.Print("Preparing update_user statement.")
update_user_stmt, err = db.Prepare("UPDATE `users` SET `name` = ?,`email` = ?,`group` = ? WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing update_group_perms statement.")
update_group_perms_stmt, err = db.Prepare("UPDATE `users_groups` SET `permissions` = ? WHERE `gid` = ?")
if err != nil {
return err
}
log.Print("Preparing update_group_rank statement.")
update_group_rank_stmt, err = db.Prepare("UPDATE `users_groups` SET `is_admin` = ?,`is_mod` = ?,`is_banned` = ? WHERE `gid` = ?")
if err != nil {
return err
}
log.Print("Preparing update_group statement.")
update_group_stmt, err = db.Prepare("UPDATE `users_groups` SET `name` = ?,`tag` = ? WHERE `gid` = ?")
if err != nil {
return err
}
log.Print("Preparing update_email statement.")
update_email_stmt, err = db.Prepare("UPDATE `emails` SET `email` = ?,`uid` = ?,`validated` = ?,`token` = ? WHERE `email` = ?")
if err != nil {
return err
}
log.Print("Preparing verify_email statement.")
verify_email_stmt, err = db.Prepare("UPDATE `emails` SET `validated` = 1,`token` = '' WHERE `email` = ?")
if err != nil {
return err
}
return nil
}

View File

@ -1,7 +1,7 @@
package main
import (
"os"
//"os"
"log"
"bytes"
"strings"
@ -14,7 +14,7 @@ import (
"html/template"
"io/ioutil"
"database/sql"
"runtime/pprof"
//"runtime/pprof"
//_ "github.com/go-sql-driver/mysql"
//"github.com/erikstmartin/go-testdb"
@ -66,6 +66,8 @@ func gloinit() {
init_static_files()
external_sites["YT"] = "https://www.youtube.com/"
//log.SetOutput(os.Stdout)
router = NewGenRouter(http.FileServer(http.Dir("./uploads")))
gloinited = true
}
@ -76,8 +78,8 @@ func init() {
func BenchmarkTopicTemplateSerial(b *testing.B) {
b.ReportAllocs()
user := User{0,"bob","Bob","bob@localhost",0,false,false,false,false,false,false,GuestPerms,"",false,"","","","","",0,0,"127.0.0.1"}
admin := User{1,"admin-alice","Admin Alice","admin@localhost",0,true,true,true,true,true,false,AllPerms,"",false,"","","","","",-1,58,"127.0.0.1"}
user := User{0,"bob","Bob","bob@localhost",0,false,false,false,false,false,false,GuestPerms,make(map[string]bool),"",false,"","","","","",0,0,"127.0.0.1"}
admin := User{1,"admin-alice","Admin Alice","admin@localhost",0,true,true,true,true,true,false,AllPerms,make(map[string]bool),"",false,"","","","","",-1,58,"127.0.0.1"}
topic := TopicUser{Title: "Lol",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,CreatedByName:"Admin",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"}
@ -164,8 +166,8 @@ func BenchmarkTopicTemplateSerial(b *testing.B) {
func BenchmarkTopicsTemplateSerial(b *testing.B) {
b.ReportAllocs()
user := User{0,"bob","Bob","bob@localhost",0,false,false,false,false,false,false,GuestPerms,"",false,"","","","","",0,0,"127.0.0.1"}
admin := User{1,"admin-alice","Admin Alice","admin@localhost",0,true,true,true,true,true,false,AllPerms,"",false,"","","","","",-1,58,"127.0.0.1"}
user := User{0,"bob","Bob","bob@localhost",0,false,false,false,false,false,false,GuestPerms,make(map[string]bool),"",false,"","","","","",0,0,"127.0.0.1"}
admin := User{1,"admin-alice","Admin Alice","admin@localhost",0,true,true,true,true,true,false,AllPerms,make(map[string]bool),"",false,"","","","","",-1,58,"127.0.0.1"}
var topicList []TopicsRow
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserSlug:"admin-alice",CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
@ -239,6 +241,8 @@ func BenchmarkStaticRouteParallel(b *testing.B) {
})
}
// TO-DO: Make these routes compatible with the changes to the router
/*
func BenchmarkTopicAdminRouteParallel(b *testing.B) {
b.ReportAllocs()
if !gloinited {
@ -287,7 +291,6 @@ func BenchmarkTopicGuestRouteParallel(b *testing.B) {
})
}
func BenchmarkForumsAdminRouteParallel(b *testing.B) {
b.ReportAllocs()
if !gloinited {
@ -371,7 +374,7 @@ func BenchmarkForumsGuestRouteParallel(b *testing.B) {
}
})
}
*/
/*func BenchmarkRoutesSerial(b *testing.B) {
b.ReportAllocs()
@ -1225,6 +1228,8 @@ func TestLevels(t *testing.T) {
}
}
// TO-DO: Make this compatible with the changes to the router
/*
func TestStaticRoute(t *testing.T) {
if !gloinited {
gloinit()
@ -1242,6 +1247,7 @@ func TestStaticRoute(t *testing.T) {
t.Fatal(static_w.Body)
}
}
*/
/*func TestTopicAdminRoute(t *testing.T) {
if !gloinited {
@ -1297,6 +1303,8 @@ func TestStaticRoute(t *testing.T) {
fmt.Println("No problems found in the topic-guest route!")
}*/
// TO-DO: Make these routes compatible with the changes to the router
/*
func TestForumsAdminRoute(t *testing.T) {
if !gloinited {
gloinit()
@ -1345,6 +1353,7 @@ func TestForumsGuestRoute(t *testing.T) {
t.Fatal(forums_w.Body)
}
}
*/
/*func TestForumAdminRoute(t *testing.T) {
if !gloinited {

View File

@ -9,10 +9,9 @@ go get -u github.com/shirou/gopsutil
echo "Installing Gorilla WebSockets"
go get -u github.com/gorilla/websocket
echo "Preparing the installer"
go generate
go build -o Gosora
echo "Building the installer"
cd ./install
go generate
go build -o Install
mv ./Install ..
cd ..

View File

@ -1,50 +1,51 @@
@echo off
echo Installing dependencies
echo Installing the dependencies
echo Installing the MySQL Driver
go get -u github.com/go-sql-driver/mysql
if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
echo Installing the PostgreSQL Driver
go get -u github.com/lib/pq
if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
echo Installing the bcrypt library
go get -u golang.org/x/crypto/bcrypt
if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
go get -u golang.org/x/sys/windows
if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
go get -u github.com/StackExchange/wmi
if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
echo Installing the gopsutil library
go get -u github.com/shirou/gopsutil
if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
echo Installing the WebSockets library
go get -u github.com/gorilla/websocket
if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
echo Preparing the installer
echo Building the installer
go generate
if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
go build -o gosora.exe
if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
go build ./install
if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
install.exe
install.exe

View File

@ -4,20 +4,27 @@ package main
import (
"fmt"
"os"
"bytes"
"bufio"
"strconv"
"io/ioutil"
"database/sql"
_ "github.com/go-sql-driver/mysql"
"runtime/debug"
"../query_gen/lib"
)
const saltLength int = 32
var db *sql.DB
var scanner *bufio.Scanner
var db_host, db_username, db_password, db_name string
//var db_collation string = "utf8mb4_general_ci"
var db_port string = "3306"
var db_adapter string = "mysql"
var db_host string
var db_username string
var db_password string
var db_name string
var db_port string
var site_name, site_url, server_port string
var default_adapter string = "mysql"
var default_host string = "localhost"
var default_username string = "root"
var default_dbname string = "gosora"
@ -25,7 +32,22 @@ var default_site_name string = "Site Name"
var default_site_url string = "localhost"
var default_server_port string = "80" // 8080's a good one, if you're testing and don't want it to clash with port 80
var init_database func()error = _init_mysql
var table_defs func()error = _table_defs_mysql
var initial_data func()error = _initial_data_mysql
func main() {
// Capture panics rather than immediately closing the window on Windows
defer func() {
r := recover()
if r != nil {
fmt.Println(r)
debug.PrintStack()
press_any_key()
return
}
}()
scanner = bufio.NewScanner(os.Stdin)
fmt.Println("Welcome to Gosora's Installer")
fmt.Println("We're going to take you through a few steps to help you get started :)")
@ -52,21 +74,8 @@ func main() {
press_any_key()
return
}
_db_password := db_password
if(_db_password != ""){
_db_password = ":" + _db_password
}
db, err := sql.Open("mysql",db_username + _db_password + "@tcp(" + db_host + ":" + db_port + ")/")
if err != nil {
fmt.Println(err)
fmt.Println("Aborting installation...")
press_any_key()
return
}
// Make sure that the connection is alive..
err = db.Ping()
err := init_database()
if err != nil {
fmt.Println(err)
fmt.Println("Aborting installation...")
@ -74,56 +83,50 @@ func main() {
return
}
fmt.Println("Successfully connected to the database")
fmt.Println("Opening the database seed file")
sqlContents, err := ioutil.ReadFile("./mysql.sql")
err = table_defs()
if err != nil {
fmt.Println(err)
fmt.Println("Aborting installation...")
press_any_key()
return
}
var waste string
err = db.QueryRow("SHOW DATABASES LIKE '" + db_name + "'").Scan(&waste)
if err != nil && err != sql.ErrNoRows {
fmt.Println(err)
fmt.Println("Aborting installation...")
press_any_key()
return
}
if err == sql.ErrNoRows {
fmt.Println("Unable to find the database. Attempting to create it")
_,err = db.Exec("CREATE DATABASE IF NOT EXISTS " + db_name + "")
if err != nil {
fmt.Println(err)
fmt.Println("Aborting installation...")
press_any_key()
return
}
fmt.Println("The database was successfully created")
}
fmt.Println("Switching to database " + db_name)
_, err = db.Exec("USE " + db_name)
hashed_password, salt, err := BcryptGeneratePassword("password")
if err != nil {
fmt.Println(err)
fmt.Println("Aborting installation...")
press_any_key()
return
}
fmt.Println("Preparing installation queries")
sqlContents = bytes.TrimSpace(sqlContents)
statements := bytes.Split(sqlContents, []byte(";"))
for key, statement := range statements {
if len(statement) == 0 {
continue
}
fmt.Println("Executing query #" + strconv.Itoa(key) + " " + string(statement))
_, err = db.Exec(string(statement))
// Build the admin user query
admin_user_stmt, err := qgen.Builder.SimpleInsert("users","name, password, salt, email, group, is_super_admin, active, createdAt, lastActiveAt, message, last_ip","'Admin',?,?,'admin@localhost',1,1,1,NOW(),NOW(),'','127.0.0.1'")
if err != nil {
fmt.Println(err)
fmt.Println("Aborting installation...")
press_any_key()
return
}
// Run the admin user query
_, err = admin_user_stmt.Exec(hashed_password,salt)
if err != nil {
fmt.Println(err)
fmt.Println("Aborting installation...")
press_any_key()
return
}
err = initial_data()
if err != nil {
fmt.Println(err)
fmt.Println("Aborting installation...")
press_any_key()
return
}
if db_adapter == "mysql" {
err = _mysql_seed_database()
if err != nil {
fmt.Println(err)
fmt.Println("Aborting installation...")
@ -131,8 +134,7 @@ func main() {
return
}
}
fmt.Println("Finished inserting the database data")
configContents := []byte(`package main
// Site Info
@ -213,6 +215,17 @@ var profiling = false
}
func get_database_details() bool {
fmt.Println("Which database driver do you wish to use? mysql, mysql, or mysql? Default: mysql")
if !scanner.Scan() {
return false
}
db_adapter = scanner.Text()
if db_adapter == "" {
db_adapter = default_adapter
}
db_adapter = set_db_adapter(db_adapter)
fmt.Println("Set database adapter to " + db_adapter)
fmt.Println("Database Host? Default: " + default_host)
if !scanner.Scan() {
return false
@ -295,6 +308,16 @@ func get_site_details() bool {
return true
}
func set_db_adapter(name string) string {
switch(name) {
//case "wip-pgsql":
// set_pgsql_adapter()
// return "wip-pgsql"
}
_set_mysql_adapter()
return "mysql"
}
func obfuscate_password(password string) (out string) {
for i := 0; i < len(password); i++ {
out += "*"

151
install/mysql.go Normal file
View File

@ -0,0 +1,151 @@
/* Copyright Azareal 2017 - 2018 */
package main
import (
"fmt"
"bytes"
"strings"
"strconv"
"io/ioutil"
"path/filepath"
"database/sql"
"../query_gen/lib"
_ "github.com/go-sql-driver/mysql"
)
//var db_collation string = "utf8mb4_general_ci"
func _set_mysql_adapter() {
db_port = "3306"
init_database = _init_mysql
table_defs = _table_defs_mysql
initial_data = _initial_data_mysql
}
func _init_mysql() (err error) {
_db_password := db_password
if _db_password != "" {
_db_password = ":" + _db_password
}
db, err = sql.Open("mysql",db_username + _db_password + "@tcp(" + db_host + ":" + db_port + ")/")
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")
var waste string
err = db.QueryRow("SHOW DATABASES LIKE '" + db_name + "'").Scan(&waste)
if err != nil && err != sql.ErrNoRows {
return err
}
if err == sql.ErrNoRows {
fmt.Println("Unable to find the database. Attempting to create it")
_,err = db.Exec("CREATE DATABASE IF NOT EXISTS " + db_name + "")
if err != nil {
return err
}
fmt.Println("The database was successfully created")
}
fmt.Println("Switching to database " + db_name)
_, err = db.Exec("USE " + db_name)
if err != nil {
return err
}
// Ready the query builder
qgen.Builder.SetConn(db)
err = qgen.Builder.SetAdapter("mysql")
if err != nil {
return err
}
return nil
}
func _table_defs_mysql() error {
//fmt.Println("Creating the tables")
files, _ := ioutil.ReadDir("./schema/mysql/")
for _, f := range files {
if !strings.HasPrefix(f.Name(),"query_") {
continue
}
var table string
var ext string
table = strings.TrimPrefix(f.Name(),"query_")
ext = filepath.Ext(table)
if ext != ".sql" {
continue
}
table = strings.TrimSuffix(table,ext)
fmt.Println("Creating table '" + table + "'")
data, err := ioutil.ReadFile("./schema/mysql/" + f.Name())
if err != nil {
return err
}
data = bytes.TrimSpace(data)
_, err = db.Exec(string(data))
if err != nil {
fmt.Println("Failed query:",string(data))
return err
}
}
//fmt.Println("Finished creating the tables")
return nil
}
func _initial_data_mysql() error {
return nil // Coming Soon
fmt.Println("Seeding the tables")
data, err := ioutil.ReadFile("./schema/mysql/inserts.sql")
if err != nil {
return err
}
data = bytes.TrimSpace(data)
fmt.Println("Executing query",string(data))
_, err = db.Exec(string(data))
if err != nil {
return err
}
//fmt.Println("Finished inserting the database data")
return nil
}
func _mysql_seed_database() error {
fmt.Println("Opening the database seed file")
sqlContents, err := ioutil.ReadFile("./mysql.sql")
if err != nil {
return err
}
fmt.Println("Preparing installation queries")
sqlContents = bytes.TrimSpace(sqlContents)
statements := bytes.Split(sqlContents, []byte(";"))
for key, statement := range statements {
if len(statement) == 0 {
continue
}
fmt.Println("Executing query #" + strconv.Itoa(key) + " " + string(statement))
_, err = db.Exec(string(statement))
if err != nil {
return err
}
}
fmt.Println("Finished inserting the database data")
return nil
}

37
install/pgsql.go Normal file
View File

@ -0,0 +1,37 @@
/* Under heavy development */
/* Copyright Azareal 2017 - 2018 */
package main
import "fmt"
import "strings"
import "database/sql"
import _ "github.com/go-sql-driver/mysql"
// We don't need SSL to run an installer... Do we?
var db_sslmode = "disable"
func _set_pgsql_adapter() {
db_port = "3306"
init_database = _init_pgsql
}
func _init_pgsql() (err error) {
_db_password := db_password
if _db_password != "" {
_db_password = " password=" + _pg_escape_bit(_db_password)
}
db, err = sql.Open("postgres", "host='" + _pg_escape_bit(db_host) + "' port='" + _pg_escape_bit(db_port) + "' user='" + _pg_escape_bit(db_username) + "' dbname='" + _pg_escape_bit(db_name) + "'" + _db_password + " sslmode='" + db_sslmode + "'")
if err != nil {
return err
}
fmt.Println("Successfully connected to the database")
// TO-DO: Create the database, if it doesn't exist
return nil
}
func _pg_escape_bit(bit string) string {
// TO-DO: Write a custom parser, so that backslashes work properly in the sql.Open string. Do something similar for the database driver, if possible?
return strings.Replace(bit,"'","\\'",-1)
}

37
install/utils.go Normal file
View File

@ -0,0 +1,37 @@
package main
import "encoding/base64"
import "crypto/rand"
import "golang.org/x/crypto/bcrypt"
// Generate a cryptographically secure set of random bytes..
func GenerateSafeString(length int) (string, error) {
rb := make([]byte,length)
_, err := rand.Read(rb)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(rb), nil
}
func BcryptGeneratePassword(password string) (hashed_password string, salt string, err error) {
salt, err = GenerateSafeString(saltLength)
if err != nil {
return "", "", err
}
password = password + salt
hashed_password, err = BcryptGeneratePasswordNoSalt(password)
if err != nil {
return "", "", err
}
return hashed_password, salt, nil
}
func BcryptGeneratePasswordNoSalt(password string) (hash string, err error) {
hashed_password, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hashed_password), nil
}

View File

@ -196,7 +196,7 @@ func main(){
///router.HandleFunc("/topics/", route_topics)
///router.HandleFunc("/forums/", route_forums)
///router.HandleFunc("/forum/", route_forum)
router.HandleFunc("/topic/create/submit/", route_create_topic)
router.HandleFunc("/topic/create/submit/", route_topic_create_submit)
router.HandleFunc("/topic/", route_topic_id)
router.HandleFunc("/reply/create/", route_create_reply)
//router.HandleFunc("/reply/edit/", route_reply_edit)

View File

@ -1,5 +1,6 @@
package main
import "strconv"
import "testing"
// TO-DO: Generate a test database to work with rather than a live one
@ -36,7 +37,61 @@ func TestUserStore(t *testing.T) {
}
if user.ID != 1 {
t.Error("user.ID doesn't not match the requested UID")
t.Error("user.ID doesn't not match the requested UID. Got '" + strconv.Itoa(user.ID) + "' instead.")
}
}
func TestForumStore(t *testing.T) {
if !gloinited {
gloinit()
}
if !plugins_inited {
init_plugins()
}
var forum *Forum
var err error
forum, err = fstore.CascadeGet(-1)
if err == nil {
t.Error("FID #-1 shouldn't exist")
} else if err != ErrNoRows {
t.Fatal(err)
}
forum, err = fstore.CascadeGet(0)
if err == ErrNoRows {
t.Error("Couldn't find FID #0")
} else if err != nil {
t.Fatal(err)
}
if forum.ID != 0 {
t.Error("forum.ID doesn't not match the requested UID. Got '" + strconv.Itoa(forum.ID) + "' instead.")
}
if forum.Name != "Uncategorised" {
t.Error("FID #0 is named '" + forum.Name + "' and not 'Uncategorised'")
}
forum, err = fstore.CascadeGet(1)
if err == ErrNoRows {
t.Error("Couldn't find FID #1")
} else if err != nil {
t.Fatal(err)
}
if forum.ID != 1 {
t.Error("forum.ID doesn't not match the requested UID. Got '" + strconv.Itoa(forum.ID) + "' instead.'")
}
if forum.Name != "Reports" {
t.Error("FID #0 is named '" + forum.Name + "' and not 'Reports'")
}
forum, err = fstore.CascadeGet(2)
if err == ErrNoRows {
t.Error("Couldn't find FID #2")
} else if err != nil {
t.Fatal(err)
}
}
@ -72,3 +127,53 @@ func TestSlugs(t *testing.T) {
}
}
}
func TestAuth(t *testing.T) {
// bcrypt likes doing stupid things, so this test will probably fail
var real_password string
var hashed_password string
var password string
var salt string
var err error
/* No extra salt tests, we might not need this extra salt, as bcrypt has it's own? */
real_password = "Madame Cassandra's Mystic Orb"
t.Log("Set real_password to '" + real_password + "'")
t.Log("Hashing the real password")
hashed_password, err = BcryptGeneratePasswordNoSalt(real_password)
if err != nil {
t.Error(err)
}
password = real_password
t.Log("Testing password '" + password + "'")
t.Log("Testing salt '" + salt + "'")
err = CheckPassword(hashed_password,password,salt)
if err == ErrMismatchedHashAndPassword {
t.Error("The two don't match")
} else if err == ErrPasswordTooLong {
t.Error("CheckPassword thinks the password is too long")
} else if err != nil {
t.Error(err)
}
password = "hahaha"
t.Log("Testing password '" + password + "'")
t.Log("Testing salt '" + salt + "'")
err = CheckPassword(hashed_password,password,salt)
if err == ErrPasswordTooLong {
t.Error("CheckPassword thinks the password is too long")
} else if err == nil {
t.Error("The two shouldn't match!")
}
password = "Madame Cassandra's Mystic"
t.Log("Testing password '" + password + "'")
t.Log("Testing salt '" + salt + "'")
err = CheckPassword(hashed_password,password,salt)
if err == ErrPasswordTooLong {
t.Error("CheckPassword thinks the password is too long")
} else if err == nil {
t.Error("The two shouldn't match!")
}
}

View File

@ -41,7 +41,7 @@ func _init_database() (err error) {
db.SetMaxOpenConns(64)
// Build the generated prepared statements, we are going to slowly move the queries over to the query generator rather than writing them all by hand, this'll make it easier for us to implement database adapters for other databases like PostgreSQL, MSSQL, SQlite, etc.
err = gen_mysql()
err = _gen_mysql()
if err != nil {
return err
}

View File

@ -1,30 +1,3 @@
CREATE TABLE `users`(
`uid` int not null AUTO_INCREMENT,
`name` varchar(100) not null,
`password` varchar(100) not null,
`salt` varchar(80) default '' not null,
`group` int not null,
`active` tinyint default 0 not null,
`is_super_admin` tinyint(1) not null,
`createdAt` datetime not null,
`lastActiveAt` datetime not null,
`session` varchar(200) default '' not null,
`last_ip` varchar(200) default '0.0.0.0.0' not null,
`email` varchar(200) default '' not null,
`avatar` varchar(20) default '' not null,
`message` text not null,
`url_prefix` varchar(20) default '' not null,
`url_name` varchar(100) default '' not null,
`level` tinyint default 0 not null,
`score` int default 0 not null,
`posts` int default 0 not null,
`bigposts` int default 0 not null,
`megaposts` int default 0 not null,
`topics` int default 0 not null,
primary key(`uid`),
unique(`name`)
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
CREATE TABLE `users_groups`(
`gid` int not null AUTO_INCREMENT,
`name` varchar(100) not null,
@ -157,8 +130,9 @@ CREATE TABLE `activity_subscriptions`(
`level` tinyint DEFAULT 0 not null /* 0: Mentions (aka the global default for any post), 1: Replies To You, 2: All Replies*/
);
/* Due to MySQL's design, we have to drop the unique keys for table settings, plugins, and themes down from 200 to 180 or it will error */
CREATE TABLE `settings`(
`name` varchar(200) not null,
`name` varchar(180) not null,
`content` varchar(250) not null,
`type` varchar(50) not null,
`constraints` varchar(200) DEFAULT '' not null,
@ -166,14 +140,14 @@ CREATE TABLE `settings`(
);
CREATE TABLE `plugins`(
`uname` varchar(200) not null,
`uname` varchar(180) not null,
`active` tinyint DEFAULT 0 not null,
`installed` tinyint DEFAULT 0 not null,
unique(`uname`)
);
CREATE TABLE `themes`(
`uname` varchar(200) not null,
`uname` varchar(180) not null,
`default` tinyint DEFAULT 0 not null,
unique(`uname`)
);
@ -215,9 +189,6 @@ INSERT INTO settings(`name`,`content`,`type`,`constraints`) VALUES ('activation_
INSERT INTO settings(`name`,`content`,`type`) VALUES ('bigpost_min_words','250','int');
INSERT INTO settings(`name`,`content`,`type`) VALUES ('megapost_min_words','1000','int');
INSERT INTO themes(`uname`,`default`) VALUES ('tempra-simple',1);
INSERT INTO users(`name`,`password`,`email`,`group`,`is_super_admin`,`createdAt`,`lastActiveAt`,`message`,`last_ip`)
VALUES ('Admin','password','admin@localhost',1,1,NOW(),NOW(),'','127.0.0.1');
INSERT INTO emails(`email`,`uid`,`validated`) VALUES ('admin@localhost',1,1);
/*

View File

@ -325,16 +325,16 @@ func route_panel_forums_delete_submit(w http.ResponseWriter, r *http.Request, us
LocalError("The provided Forum ID is not a valid number.",w,r,user)
return
}
if !fstore.Exists(fid) {
LocalError("The forum you're trying to delete doesn't exist.",w,r,user)
return
}
err = fstore.CascadeDelete(fid)
if err != nil {
if err == ErrNoRows {
LocalError("The forum you're trying to delete doesn't exist.",w,r,user)
return
} else if err != nil {
InternalError(err,w,r)
return
}
http.Redirect(w,r,"/panel/forums/",http.StatusSeeOther)
}

View File

@ -30,12 +30,13 @@ func _init_database() (err error) {
return err
}
// TO-DO: Get the version number
// Fetch the database version
db.QueryRow("SELECT VERSION()").Scan(&db_version)
// Set the number of max open connections. How many do we need? Might need to do some tests.
db.SetMaxOpenConns(64)
err = gen_pgsql()
err = _gen_pgsql()
if err != nil {
return err
}

View File

@ -374,7 +374,13 @@ func bbcode_full_parse(msg string) string {
goto MainLoop
}
dat := []byte(strconv.FormatInt((random.Int63n(number)),10))
var dat []byte
if number == 0 {
dat = []byte("0")
} else {
dat = []byte(strconv.FormatInt((random.Int63n(number)),10))
}
outbytes = append(outbytes, dat...)
//log.Print("Outputted the random number")
i += 7

View File

@ -40,6 +40,7 @@ type SocialGroup struct
CreatedAt string
LastUpdateTime string
MainForumID int
MainForum *Forum
Forums []*Forum
ExtData ExtData
@ -316,6 +317,12 @@ func socialgroups_group_list(w http.ResponseWriter, r *http.Request, user User)
}
}
func socialgroups_get_group(sgid int) (sgItem SocialGroup, err error) {
sgItem = SocialGroup{ID:sgid}
err = socialgroups_get_group_stmt.QueryRow(sgid).Scan(&sgItem.Name, &sgItem.Desc, &sgItem.Active, &sgItem.Privacy, &sgItem.Owner, &sgItem.MemberCount, &sgItem.MainForumID, &sgItem.Backdrop, &sgItem.CreatedAt, &sgItem.LastUpdateTime)
return sgItem, err
}
func socialgroups_view_group(w http.ResponseWriter, r *http.Request, user User) {
// SEO URLs...
halves := strings.Split(r.URL.Path[len("/group/"):],".")
@ -328,9 +335,7 @@ func socialgroups_view_group(w http.ResponseWriter, r *http.Request, user User)
return
}
var sgItem SocialGroup = SocialGroup{ID:sgid}
var mainForum int
err = socialgroups_get_group_stmt.QueryRow(sgid).Scan(&sgItem.Name, &sgItem.Desc, &sgItem.Active, &sgItem.Privacy, &sgItem.Owner, &sgItem.MemberCount, &mainForum, &sgItem.Backdrop, &sgItem.CreatedAt, &sgItem.LastUpdateTime)
sgItem, err := socialgroups_get_group(sgid)
if err != nil {
LocalError("Bad group",w,r,user)
return
@ -341,7 +346,7 @@ func socialgroups_view_group(w http.ResponseWriter, r *http.Request, user User)
// Re-route the request to route_forums
var ctx context.Context = context.WithValue(r.Context(),"socialgroups_current_group",sgItem)
route_forum(w,r.WithContext(ctx),user,strconv.Itoa(mainForum))
route_forum(w,r.WithContext(ctx),user,strconv.Itoa(sgItem.MainForumID))
}
func socialgroups_create_group(w http.ResponseWriter, r *http.Request, user User) {
@ -557,21 +562,35 @@ func socialgroups_topic_create_pre_loop(args ...interface{}) interface{} {
return nil
}
// TO-DO: Permissions Override. It doesn't quite work yet.
// TO-DO: Add privacy options
// TO-DO: Add support for multiple boards and add per-board simplified permissions
// TO-DO: Take is_js into account for routes which expect JSON responses
func socialgroups_forum_check(args ...interface{}) (skip interface{}) {
var r = args[1].(*http.Request)
var fid *int = args[3].(*int)
if fstore.DirtyGet(*fid).ParentType == "socialgroup" {
var forum *Forum = fstore.DirtyGet(*fid)
if forum.ParentType == "socialgroup" {
var err error
var w = args[0].(http.ResponseWriter)
var success *bool = args[4].(*bool)
sgItem, ok := r.Context().Value("socialgroups_current_group").(SocialGroup)
if !ok {
LogError(errors.New("Unable to find a parent group in the context data"))
return false
sgItem, err = socialgroups_get_group(forum.ParentID)
if err != nil {
InternalError(errors.New("Unable to find the parent group for a forum"),w,r)
*success = false
return false
}
if !sgItem.Active {
NotFound(w,r)
*success = false
return false
}
r = r.WithContext(context.WithValue(r.Context(),"socialgroups_current_group",sgItem))
}
//run_vhook("simple_forum_check_pre_perms", w, r, user, &fid, &success).(bool)
var w = args[0].(http.ResponseWriter)
var user *User = args[2].(*User)
var success *bool = args[4].(*bool)
var rank int
var posts int
var joinedAt string
@ -583,18 +602,18 @@ func socialgroups_forum_check(args ...interface{}) (skip interface{}) {
override_forum_perms(&user.Perms, false)
user.Perms.ViewTopic = true
err := socialgroups_get_member_stmt.QueryRow(sgItem.ID,user.ID).Scan(&rank,&posts,&joinedAt)
err = socialgroups_get_member_stmt.QueryRow(sgItem.ID,user.ID).Scan(&rank,&posts,&joinedAt)
if err != nil && err != ErrNoRows {
*success = false
InternalError(err,w,r)
return false
} else if err != nil {
return false
return true
}
// TO-DO: Implement bans properly by adding the Local Ban API in the next commit
if rank < 0 {
return false
return true
}
// Basic permissions for members, more complicated permissions coming in the next commit!
@ -607,6 +626,7 @@ func socialgroups_forum_check(args ...interface{}) (skip interface{}) {
} else {
override_forum_perms(&user.Perms,true)
}
return true
}
return false

View File

@ -30,9 +30,13 @@ func TestBBCodeRender(t *testing.T) {
msgList = addMEPair(msgList,"[b]hi[/i]","[b]hi[/i]")
msgList = addMEPair(msgList,"[/b]hi[b]","[/b]hi[b]")
msgList = addMEPair(msgList,"[/b]hi[/b]","[/b]hi[/b]")
msgList = addMEPair(msgList,"[b][b]hi[/b]","<b>hi</b>")
msgList = addMEPair(msgList,"[b][b]hi","[b][b]hi")
msgList = addMEPair(msgList,"[b][b][b]hi","[b][b][b]hi")
msgList = addMEPair(msgList,"[/b]hi","[/b]hi")
msgList = addMEPair(msgList,"[code]hi[/code]","<span class='codequotes'>hi</span>")
msgList = addMEPair(msgList,"[code][b]hi[/b][/code]","<span class='codequotes'>[b]hi[/b]</span>")
msgList = addMEPair(msgList,"[code][b]hi[/code][/b]","<span class='codequotes'>[b]hi</span>[/b]")
msgList = addMEPair(msgList,"[quote]hi[/quote]","<span class='postQuote'>hi</span>")
msgList = addMEPair(msgList,"[quote][b]hi[/b][/quote]","<span class='postQuote'><b>hi</b></span>")
msgList = addMEPair(msgList,"[quote][b]h[/b][/quote]","<span class='postQuote'><b>h</b></span>")
@ -41,7 +45,7 @@ func TestBBCodeRender(t *testing.T) {
t.Log("Testing bbcode_full_parse")
for _, item := range msgList {
t.Log("Testing string '"+item.Msg+"'")
res = bbcode_full_parse(item.Msg).(string)
res = bbcode_full_parse(item.Msg)
if res != item.Expects {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected:",item.Expects)
@ -54,7 +58,7 @@ func TestBBCodeRender(t *testing.T) {
msg = "[rand][/rand]"
expects = "<span style='color: red;'>[Invalid Number]</span>[rand][/rand]"
t.Log("Testing string '"+msg+"'")
res = bbcode_full_parse(msg).(string)
res = bbcode_full_parse(msg)
if res != expects {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected:",expects)
@ -63,7 +67,52 @@ func TestBBCodeRender(t *testing.T) {
msg = "[rand]-1[/rand]"
expects = "<span style='color: red;'>[No Negative Numbers]</span>[rand]-1[/rand]"
t.Log("Testing string '"+msg+"'")
res = bbcode_full_parse(msg).(string)
res = bbcode_full_parse(msg)
if res != expects {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected:",expects)
}
msg = "[rand]-01[/rand]"
expects = "<span style='color: red;'>[No Negative Numbers]</span>[rand]-01[/rand]"
t.Log("Testing string '"+msg+"'")
res = bbcode_full_parse(msg)
if res != expects {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected:",expects)
}
msg = "[rand]NaN[/rand]"
expects = "<span style='color: red;'>[Invalid Number]</span>[rand]NaN[/rand]"
t.Log("Testing string '"+msg+"'")
res = bbcode_full_parse(msg)
if res != expects {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected:",expects)
}
msg = "[rand]Inf[/rand]"
expects = "<span style='color: red;'>[Invalid Number]</span>[rand]Inf[/rand]"
t.Log("Testing string '"+msg+"'")
res = bbcode_full_parse(msg)
if res != expects {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected:",expects)
}
msg = "[rand]+[/rand]"
expects = "<span style='color: red;'>[Invalid Number]</span>[rand]+[/rand]"
t.Log("Testing string '"+msg+"'")
res = bbcode_full_parse(msg)
if res != expects {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected:",expects)
}
msg = "[rand]1+1[/rand]"
expects = "<span style='color: red;'>[Invalid Number]</span>[rand]1+1[/rand]"
t.Log("Testing string '"+msg+"'")
res = bbcode_full_parse(msg)
if res != expects {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected:",expects)
@ -72,17 +121,62 @@ func TestBBCodeRender(t *testing.T) {
var conv int
msg = "[rand]1[/rand]"
t.Log("Testing string '"+msg+"'")
res = bbcode_full_parse(msg).(string)
res = bbcode_full_parse(msg)
conv, err = strconv.Atoi(res)
if err != nil && (conv > 1 || conv < 0) {
if err != nil || (conv > 1 || conv < 0) {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected a number in the range 0-1")
}
msg = "[rand]0[/rand]"
t.Log("Testing string '"+msg+"'")
res = bbcode_full_parse(msg)
conv, err = strconv.Atoi(res)
if err != nil || conv != 0 {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected the number 0")
}
msg = "[rand]2147483647[/rand]" // Signed 32-bit MAX
t.Log("Testing string '"+msg+"'")
res = bbcode_full_parse(msg)
conv, err = strconv.Atoi(res)
if err != nil || (conv > 2147483647 || conv < 0) {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected a number between 0 and 2147483647")
}
msg = "[rand]9223372036854775807[/rand]" // Signed 64-bit MAX
t.Log("Testing string '"+msg+"'")
res = bbcode_full_parse(msg)
conv, err = strconv.Atoi(res)
if err != nil || (conv > 9223372036854775807 || conv < 0) {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected a number between 0 and 9223372036854775807")
}
// Note: conv is commented out in these two, as these numbers overflow int
msg = "[rand]18446744073709551615[/rand]" // Unsigned 64-bit MAX
t.Log("Testing string '"+msg+"'")
res = bbcode_full_parse(msg)
conv, err = strconv.Atoi(res)
if err != nil || (/*conv > 18446744073709551615 || */conv < 0) {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected a number between 0 and 18446744073709551615")
}
msg = "[rand]170141183460469231731687303715884105727[/rand]" // Signed 128-bit MAX
t.Log("Testing string '"+msg+"'")
res = bbcode_full_parse(msg)
conv, err = strconv.Atoi(res)
if err != nil || (/*conv > 170141183460469231731687303715884105727 || */conv < 0) {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected a number between 0 and 170141183460469231731687303715884105727")
}
t.Log("Testing bbcode_regex_parse")
for _, item := range msgList {
t.Log("Testing string '"+item.Msg+"'")
res = bbcode_regex_parse(item.Msg).(string)
res = bbcode_regex_parse(item.Msg)
if res != item.Expects {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected:",item.Expects)
@ -123,7 +217,7 @@ func TestMarkdownRender(t *testing.T) {
for _, item := range msgList {
t.Log("Testing string '"+item.Msg+"'")
res = markdown_parse(item.Msg).(string)
res = markdown_parse(item.Msg)
if res != item.Expects {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected:",item.Expects)

61
query_gen/lib/install.go Normal file
View File

@ -0,0 +1,61 @@
/* WIP Under Construction */
package qgen
var Install *installer
func init() {
Install = &installer{instructions:[]DB_Install_Instruction{}}
}
type DB_Install_Instruction struct
{
Table string
Contents string
Type string
}
// A set of wrappers around the generator methods, so we can use this in the installer
// TO-DO: Re-implement the query generation, query builder and installer adapters as layers on-top of a query text adapter
type installer struct
{
adapter DB_Adapter
instructions []DB_Install_Instruction
}
func (install *installer) SetAdapter(name string) error {
adap, err := GetAdapter(name)
if err != nil {
return err
}
install.adapter = adap
return nil
}
func (install *installer) SetAdapterInstance(adapter DB_Adapter) {
install.adapter = adapter
}
func (install *installer) CreateTable(table string, charset string, collation string, columns []DB_Table_Column, keys []DB_Table_Key) error {
res, err := install.adapter.CreateTable("_installer", table, charset, collation, columns, keys)
if err != nil {
return err
}
install.instructions = append(install.instructions,DB_Install_Instruction{table,res,"create-table"})
return nil
}
func (install *installer) Write() error {
var inserts string
// We can't escape backticks, so we have to dump it out a file at a time
for _, instr := range install.instructions {
if instr.Type == "create-table" {
err := write_file("./schema/" + install.adapter.GetName() + "/query_" + instr.Table + ".sql", instr.Contents)
if err != nil {
return err
}
} else {
inserts += instr.Contents + "\n"
}
}
return write_file("./schema/" + install.adapter.GetName() + "/inserts.sql", inserts)
}

View File

@ -8,14 +8,14 @@ import "errors"
func init() {
DB_Registry = append(DB_Registry,
&Mysql_Adapter{Name:"mysql",Buffer:make(map[string]string)},
&Mysql_Adapter{Name:"mysql",Buffer:make(map[string]DB_Stmt)},
)
}
type Mysql_Adapter struct
{
Name string
Buffer map[string]string
Buffer map[string]DB_Stmt
BufferOrder []string // Map iteration order is random, so we need this to track the order, so we don't get huge diffs every commit
}
@ -23,11 +23,11 @@ func (adapter *Mysql_Adapter) GetName() string {
return adapter.Name
}
func (adapter *Mysql_Adapter) GetStmt(name string) string {
func (adapter *Mysql_Adapter) GetStmt(name string) DB_Stmt {
return adapter.Buffer[name]
}
func (adapter *Mysql_Adapter) GetStmts() map[string]string {
func (adapter *Mysql_Adapter) GetStmts() map[string]DB_Stmt {
return adapter.Buffer
}
@ -55,9 +55,10 @@ func (adapter *Mysql_Adapter) CreateTable(name string, table string, charset str
}
var end string
if column.Default != "" {
// TO-DO: Exclude the other variants of text like mediumtext and longtext too
if column.Default != "" && column.Type != "text" {
end = " DEFAULT "
if adapter.stringy_type(column.Type) {
if adapter.stringy_type(column.Type) && column.Default != "''" {
end += "'" + column.Default + "'"
} else {
end += column.Default
@ -79,7 +80,11 @@ func (adapter *Mysql_Adapter) CreateTable(name string, table string, charset str
if len(keys) > 0 {
for _, key := range keys {
querystr += "\n\t" + key.Type + " key("
querystr += "\n\t" + key.Type
if key.Type != "unique" {
querystr += " key"
}
querystr += "("
for _, column := range strings.Split(key.Columns,",") {
querystr += "`" + column + "`,"
}
@ -95,7 +100,7 @@ func (adapter *Mysql_Adapter) CreateTable(name string, table string, charset str
querystr += " COLLATE " + collation
}
adapter.push_statement(name,querystr + ";")
adapter.push_statement(name,"create-table",querystr + ";")
return querystr + ";", nil
}
@ -133,7 +138,7 @@ func (adapter *Mysql_Adapter) SimpleInsert(name string, table string, columns st
}
querystr = querystr[0:len(querystr) - 1]
adapter.push_statement(name,querystr + ")")
adapter.push_statement(name,"insert",querystr + ")")
return querystr + ")", nil
}
@ -170,7 +175,7 @@ func (adapter *Mysql_Adapter) SimpleReplace(name string, table string, columns s
}
querystr = querystr[0:len(querystr) - 1]
adapter.push_statement(name,querystr + ")")
adapter.push_statement(name,"replace",querystr + ")")
return querystr + ")", nil
}
@ -225,7 +230,7 @@ func (adapter *Mysql_Adapter) SimpleUpdate(name string, table string, set string
querystr = querystr[0:len(querystr) - 4]
}
adapter.push_statement(name,querystr)
adapter.push_statement(name,"update",querystr)
return querystr, nil
}
@ -260,7 +265,7 @@ func (adapter *Mysql_Adapter) SimpleDelete(name string, table string, where stri
}
querystr = strings.TrimSpace(querystr[0:len(querystr) - 4])
adapter.push_statement(name,querystr)
adapter.push_statement(name,"delete",querystr)
return querystr, nil
}
@ -272,7 +277,7 @@ func (adapter *Mysql_Adapter) Purge(name string, table string) (string, error) {
if table == "" {
return "", errors.New("You need a name for this table")
}
adapter.push_statement(name,"DELETE FROM `" + table + "`")
adapter.push_statement(name,"purge","DELETE FROM `" + table + "`")
return "DELETE FROM `" + table + "`", nil
}
@ -335,7 +340,7 @@ func (adapter *Mysql_Adapter) SimpleSelect(name string, table string, columns st
}
querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,querystr)
adapter.push_statement(name,"select",querystr)
return querystr, nil
}
@ -425,7 +430,7 @@ func (adapter *Mysql_Adapter) SimpleLeftJoin(name string, table1 string, table2
}
querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,querystr)
adapter.push_statement(name,"select",querystr)
return querystr, nil
}
@ -515,7 +520,7 @@ func (adapter *Mysql_Adapter) SimpleInnerJoin(name string, table1 string, table2
}
querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,querystr)
adapter.push_statement(name,"select",querystr)
return querystr, nil
}
@ -589,7 +594,7 @@ func (adapter *Mysql_Adapter) SimpleInsertSelect(name string, ins DB_Insert, sel
}
querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,querystr)
adapter.push_statement(name,"insert",querystr)
return querystr, nil
}
@ -674,7 +679,7 @@ func (adapter *Mysql_Adapter) SimpleInsertLeftJoin(name string, ins DB_Insert, s
}
querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,querystr)
adapter.push_statement(name,"insert",querystr)
return querystr, nil
}
@ -759,7 +764,7 @@ func (adapter *Mysql_Adapter) SimpleInsertInnerJoin(name string, ins DB_Insert,
}
querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,querystr)
adapter.push_statement(name,"insert",querystr)
return querystr, nil
}
@ -802,34 +807,38 @@ func (adapter *Mysql_Adapter) SimpleCount(name string, table string, where strin
}
querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,querystr)
adapter.push_statement(name,"select",querystr)
return querystr, nil
}
func (adapter *Mysql_Adapter) Write() error {
var stmts, body string
for _, name := range adapter.BufferOrder {
stmts += "var " + name + "_stmt *sql.Stmt\n"
body += `
stmt := adapter.Buffer[name]
// TO-DO: Add support for create-table? Table creation might be a little complex for Go to do outside a SQL file :(
if stmt.Type != "create-table" {
stmts += "var " + name + "_stmt *sql.Stmt\n"
body += `
log.Print("Preparing ` + name + ` statement.")
` + name + `_stmt, err = db.Prepare("` + adapter.Buffer[name] + `")
` + name + `_stmt, err = db.Prepare("` + stmt.Contents + `")
if err != nil {
return err
}
`
}
}
out := `// Code generated by Gosora. More below:
out := `// +build !pgsql !sqlite !mssql
/* This file was generated by Gosora's Query Generator. Please try to avoid modifying this file, as it might change at any time. */
// +build !pgsql !sqlite !mssql
package main
import "log"
import "database/sql"
` + stmts + `
func gen_mysql() (err error) {
func _gen_mysql() (err error) {
if debug_mode {
log.Print("Building the generated statements")
}
@ -841,8 +850,8 @@ func gen_mysql() (err error) {
}
// Internal methods, not exposed in the interface
func (adapter *Mysql_Adapter) push_statement(name string, querystr string) {
adapter.Buffer[name] = querystr
func (adapter *Mysql_Adapter) push_statement(name string, stype string, querystr string) {
adapter.Buffer[name] = DB_Stmt{querystr,stype}
adapter.BufferOrder = append(adapter.BufferOrder,name)
}

338
query_gen/lib/pgsql.go Normal file
View File

@ -0,0 +1,338 @@
/* WIP Under *Heavy* Construction */
package qgen
import "strings"
import "strconv"
import "errors"
func init() {
DB_Registry = append(DB_Registry,
&Pgsql_Adapter{Name:"pgsql",Buffer:make(map[string]DB_Stmt)},
)
}
type Pgsql_Adapter struct
{
Name string
Buffer map[string]DB_Stmt
BufferOrder []string // Map iteration order is random, so we need this to track the order, so we don't get huge diffs every commit
}
func (adapter *Pgsql_Adapter) GetName() string {
return adapter.Name
}
func (adapter *Pgsql_Adapter) GetStmt(name string) DB_Stmt {
return adapter.Buffer[name]
}
func (adapter *Pgsql_Adapter) GetStmts() map[string]DB_Stmt {
return adapter.Buffer
}
// TO-DO: Implement this
// We may need to change the CreateTable API to better suit PGSQL and the other database drivers which are coming up
func (adapter *Pgsql_Adapter) CreateTable(name string, table string, charset string, collation string, columns []DB_Table_Column, keys []DB_Table_Key) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table == "" {
return "", errors.New("You need a name for this table")
}
if len(columns) == 0 {
return "", errors.New("You can't have a table with no columns")
}
var querystr string = "CREATE TABLE `" + table + "` ("
for _, column := range columns {
if column.Auto_Increment {
column.Type = "serial"
} else if column.Type == "createdAt" {
column.Type = "timestamp"
} else if column.Type == "datetime" {
column.Type = "timestamp"
}
var size string
if column.Size > 0 {
size = " (" + strconv.Itoa(column.Size) + ")"
}
var end string
if column.Default != "" {
end = " DEFAULT "
if adapter.stringy_type(column.Type) && column.Default != "''" {
end += "'" + column.Default + "'"
} else {
end += column.Default
}
}
if !column.Null {
end += " not null"
}
querystr += "\n\t`"+column.Name+"` " + column.Type + size + end + ","
}
if len(keys) > 0 {
for _, key := range keys {
querystr += "\n\t" + key.Type
if key.Type != "unique" {
querystr += " key"
}
querystr += "("
for _, column := range strings.Split(key.Columns,",") {
querystr += "`" + column + "`,"
}
querystr = querystr[0:len(querystr) - 1] + "),"
}
}
querystr = querystr[0:len(querystr) - 1] + "\n);"
adapter.push_statement(name,"create-table",querystr)
return querystr, nil
}
// TO-DO: Implement this
func (adapter *Pgsql_Adapter) SimpleInsert(name string, table string, columns string, fields string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table == "" {
return "", errors.New("You need a name for this table")
}
if len(columns) == 0 {
return "", errors.New("No columns found for SimpleInsert")
}
if len(fields) == 0 {
return "", errors.New("No input data found for SimpleInsert")
}
return "", nil
}
// TO-DO: Implement this
func (adapter *Pgsql_Adapter) SimpleReplace(name string, table string, columns string, fields string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table == "" {
return "", errors.New("You need a name for this table")
}
if len(columns) == 0 {
return "", errors.New("No columns found for SimpleInsert")
}
if len(fields) == 0 {
return "", errors.New("No input data found for SimpleInsert")
}
return "", nil
}
// TO-DO: Implemented, but we need CreateTable and a better installer to *test* it
func (adapter *Pgsql_Adapter) SimpleUpdate(name string, table string, set string, where string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table == "" {
return "", errors.New("You need a name for this table")
}
if set == "" {
return "", errors.New("You need to set data in this update statement")
}
var querystr string = "UPDATE `" + table + "` SET "
for _, item := range _process_set(set) {
querystr += "`" + item.Column + "` ="
for _, token := range item.Expr {
switch(token.Type) {
case "function","operator","number","substitute":
querystr += " " + token.Contents + ""
case "column":
querystr += " `" + token.Contents + "`"
case "string":
querystr += " '" + token.Contents + "'"
}
}
querystr += ","
}
// Remove the trailing comma
querystr = querystr[0:len(querystr) - 1]
// Add support for BETWEEN x.x
if len(where) != 0 {
querystr += " WHERE"
for _, loc := range _process_where(where) {
for _, token := range loc.Expr {
switch(token.Type) {
case "function","operator","number","substitute":
querystr += " " + token.Contents + ""
case "column":
querystr += " `" + token.Contents + "`"
case "string":
querystr += " '" + token.Contents + "'"
default:
panic("This token doesn't exist o_o")
}
}
querystr += " AND"
}
querystr = querystr[0:len(querystr) - 4]
}
adapter.push_statement(name,"update",querystr)
return querystr, nil
}
// TO-DO: Implement this
func (adapter *Pgsql_Adapter) SimpleDelete(name string, table string, where string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table == "" {
return "", errors.New("You need a name for this table")
}
if where == "" {
return "", errors.New("You need to specify what data you want to delete")
}
return "", nil
}
// TO-DO: Implement this
// We don't want to accidentally wipe tables, so we'll have a seperate method for purging tables instead
func (adapter *Pgsql_Adapter) Purge(name string, table string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table == "" {
return "", errors.New("You need a name for this table")
}
return "", nil
}
// TO-DO: Implement this
func (adapter *Pgsql_Adapter) SimpleSelect(name string, table string, columns string, where string, orderby string, limit string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table == "" {
return "", errors.New("You need a name for this table")
}
if len(columns) == 0 {
return "", errors.New("No columns found for SimpleSelect")
}
return "", nil
}
// TO-DO: Implement this
func (adapter *Pgsql_Adapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table1 == "" {
return "", errors.New("You need a name for the left table")
}
if table2 == "" {
return "", errors.New("You need a name for the right table")
}
if len(columns) == 0 {
return "", errors.New("No columns found for SimpleLeftJoin")
}
if len(joiners) == 0 {
return "", errors.New("No joiners found for SimpleLeftJoin")
}
return "", nil
}
// TO-DO: Implement this
func (adapter *Pgsql_Adapter) SimpleInnerJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table1 == "" {
return "", errors.New("You need a name for the left table")
}
if table2 == "" {
return "", errors.New("You need a name for the right table")
}
if len(columns) == 0 {
return "", errors.New("No columns found for SimpleInnerJoin")
}
if len(joiners) == 0 {
return "", errors.New("No joiners found for SimpleInnerJoin")
}
return "", nil
}
// TO-DO: Implement this
func (adapter *Pgsql_Adapter) SimpleInsertSelect(name string, ins DB_Insert, sel DB_Select) (string, error) {
return "", nil
}
// TO-DO: Implement this
func (adapter *Pgsql_Adapter) SimpleInsertLeftJoin(name string, ins DB_Insert, sel DB_Join) (string, error) {
return "", nil
}
// TO-DO: Implement this
func (adapter *Pgsql_Adapter) SimpleInsertInnerJoin(name string, ins DB_Insert, sel DB_Join) (string, error) {
return "", nil
}
// TO-DO: Implement this
func (adapter *Pgsql_Adapter) SimpleCount(name string, table string, where string, limit string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table == "" {
return "", errors.New("You need a name for this table")
}
return "", nil
}
func (adapter *Pgsql_Adapter) Write() error {
var stmts, body string
for _, name := range adapter.BufferOrder {
stmt := adapter.Buffer[name]
// TO-DO: Add support for create-table? Table creation might be a little complex for Go to do outside a SQL file :(
if stmt.Type != "create-table" {
stmts += "var " + name + "_stmt *sql.Stmt\n"
body += `
log.Print("Preparing ` + name + ` statement.")
` + name + `_stmt, err = db.Prepare("` + stmt.Contents + `")
if err != nil {
return err
}
`
}
}
out := `// +build pgsql
// This file was generated by Gosora's Query Generator. Please try to avoid modifying this file, as it might change at any time.
package main
import "log"
import "database/sql"
` + stmts + `
func _gen_pgsql() (err error) {
if debug_mode {
log.Print("Building the generated statements")
}
` + body + `
return nil
}
`
return write_file("./gen_pgsql.go", out)
}
// Internal methods, not exposed in the interface
func (adapter *Pgsql_Adapter) push_statement(name string, stype string, querystr string) {
adapter.Buffer[name] = DB_Stmt{querystr,stype}
adapter.BufferOrder = append(adapter.BufferOrder,name)
}
func (adapter *Pgsql_Adapter) stringy_type(ctype string) bool {
ctype = strings.ToLower(ctype)
return ctype == "char" || ctype == "varchar" || ctype == "timestamp" || ctype == "text"
}

View File

@ -98,6 +98,12 @@ type DB_Limit struct {
MaxCount string // ? or int
}
type DB_Stmt struct
{
Contents string
Type string // create-table, insert, update, delete
}
type DB_Adapter interface {
GetName() string
CreateTable(name string, table string, charset string, collation string, columns []DB_Table_Column, keys []DB_Table_Key) (string, error)

View File

@ -8,13 +8,25 @@ func main() {
log.Println("Running the query generator")
for _, adapter := range qgen.DB_Registry {
log.Println("Building the queries for the " + adapter.GetName() + " adapter")
qgen.Install.SetAdapterInstance(adapter)
write_statements(adapter)
qgen.Install.Write()
adapter.Write()
}
}
func write_statements(adapter qgen.DB_Adapter) error {
err := write_selects(adapter)
err := create_tables(adapter)
if err != nil {
return err
}
err = seed_tables(adapter)
if err != nil {
return err
}
err = write_selects(adapter)
if err != nil {
return err
}
@ -72,6 +84,45 @@ func write_statements(adapter qgen.DB_Adapter) error {
return nil
}
func create_tables(adapter qgen.DB_Adapter) error {
qgen.Install.CreateTable("users","utf8mb4","utf8mb4_general_ci",
[]qgen.DB_Table_Column{
qgen.DB_Table_Column{"uid","int",0,false,true,""},
qgen.DB_Table_Column{"name","varchar",100,false,false,""},
qgen.DB_Table_Column{"password","varchar",100,false,false,""},
qgen.DB_Table_Column{"salt","varchar",80,false,false,"''"},
qgen.DB_Table_Column{"group","int",0,false,false,""},
qgen.DB_Table_Column{"active","boolean",0,false,false,"0"},
qgen.DB_Table_Column{"is_super_admin","boolean",0,false,false,"0"},
qgen.DB_Table_Column{"createdAt","createdAt",0,false,false,""},
qgen.DB_Table_Column{"lastActiveAt","datetime",0,false,false,""},
qgen.DB_Table_Column{"session","varchar",200,false,false,"''"},
qgen.DB_Table_Column{"last_ip","varchar",200,false,false,"0.0.0.0.0"},
qgen.DB_Table_Column{"email","varchar",200,false,false,"''"},
qgen.DB_Table_Column{"avatar","varchar",100,false,false,"''"},
qgen.DB_Table_Column{"message","text",0,false,false,"''"},
qgen.DB_Table_Column{"url_prefix","varchar",20,false,false,"''"},
qgen.DB_Table_Column{"url_name","varchar",100,false,false,"''"},
qgen.DB_Table_Column{"level","smallint",0,false,false,"0"},
qgen.DB_Table_Column{"score","int",0,false,false,"0"},
qgen.DB_Table_Column{"posts","int",0,false,false,"0"},
qgen.DB_Table_Column{"bigposts","int",0,false,false,"0"},
qgen.DB_Table_Column{"megaposts","int",0,false,false,"0"},
qgen.DB_Table_Column{"topics","int",0,false,false,"0"},
},
[]qgen.DB_Table_Key{
qgen.DB_Table_Key{"uid","primary"},
qgen.DB_Table_Key{"name","unique"},
},
)
return nil
}
func seed_tables(adapter qgen.DB_Adapter) error {
return nil
}
func write_selects(adapter qgen.DB_Adapter) error {
// url_prefix and url_name will be removed from this query in a later commit
adapter.SimpleSelect("get_user","users","name, group, is_super_admin, avatar, message, url_prefix, url_name, level","uid = ?","","")

333
routes.go
View File

@ -17,7 +17,6 @@ import (
"html/template"
"./query_gen/lib"
"golang.org/x/crypto/bcrypt"
)
// A blank list to fill out that parameter in Page for routes which don't use it
@ -68,22 +67,28 @@ func route_fstatic(w http.ResponseWriter, r *http.Request){
http.ServeFile(w,r,r.URL.Path)
}*/
func route_overview(w http.ResponseWriter, r *http.Request){
user, headerVars, ok := SessionCheck(w,r)
func route_overview(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
return
}
BuildWidgets("overview",nil,&headerVars)
BuildWidgets("overview",nil,&headerVars,r)
pi := Page{"Overview",user,headerVars,tList,nil}
if pre_render_hooks["pre_render_overview"] != nil {
if run_pre_render_hook("pre_render_overview", w, r, &user, &pi) {
return
}
}
err := templates.ExecuteTemplate(w,"overview.html",pi)
if err != nil {
InternalError(err,w,r)
}
}
func route_custom_page(w http.ResponseWriter, r *http.Request){
user, headerVars, ok := SessionCheck(w,r)
func route_custom_page(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
return
}
@ -93,20 +98,27 @@ func route_custom_page(w http.ResponseWriter, r *http.Request){
NotFound(w,r)
return
}
BuildWidgets("custom_page",name,&headerVars)
BuildWidgets("custom_page",name,&headerVars,r)
err := templates.ExecuteTemplate(w,"page_" + name,Page{"Page",user,headerVars,tList,nil})
pi := Page{"Page",user,headerVars,tList,nil}
if pre_render_hooks["pre_render_custom_page"] != nil {
if run_pre_render_hook("pre_render_custom_page", w, r, &user, &pi) {
return
}
}
err := templates.ExecuteTemplate(w,"page_" + name,pi)
if err != nil {
InternalError(err,w,r)
}
}
func route_topics(w http.ResponseWriter, r *http.Request){
user, headerVars, ok := SessionCheck(w,r)
func route_topics(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
return
}
BuildWidgets("topics",nil,&headerVars)
BuildWidgets("topics",nil,&headerVars,r)
var qlist string
var fidList []interface{}
@ -151,8 +163,10 @@ func route_topics(w http.ResponseWriter, r *http.Request){
topicItem.Avatar = strings.Replace(noavatar,"{id}",strconv.Itoa(topicItem.CreatedBy),1)
}
forum := fstore.DirtyGet(topicItem.ParentID)
if topicItem.ParentID >= 0 {
topicItem.ForumName = fstore.DirtyGet(topicItem.ParentID).Name
topicItem.ForumName = forum.Name
topicItem.ForumLink = forum.Link
} else {
topicItem.ForumName = ""
}
@ -166,8 +180,8 @@ func route_topics(w http.ResponseWriter, r *http.Request){
InternalError(err,w,r)
}
if hooks["trow_assign"] != nil {
topicItem = run_hook("trow_assign", topicItem).(TopicsRow)
if hooks["topics_trow_assign"] != nil {
run_vhook("topics_trow_assign", &topicItem, &forum)
}
topicList = append(topicList, topicItem)
}
@ -179,6 +193,12 @@ func route_topics(w http.ResponseWriter, r *http.Request){
rows.Close()
pi := TopicsPage{"Topic List",user,headerVars,topicList,extData}
if pre_render_hooks["pre_render_topic_list"] != nil {
if run_pre_render_hook("pre_render_topic_list", w, r, &user, &pi) {
return
}
}
if template_topics_handle != nil {
template_topics_handle(pi,w)
} else {
@ -193,7 +213,7 @@ func route_topics(w http.ResponseWriter, r *http.Request){
}
}
func route_forum(w http.ResponseWriter, r *http.Request, sfid string){
func route_forum(w http.ResponseWriter, r *http.Request, user User, sfid string){
page, _ := strconv.Atoi(r.FormValue("page"))
// SEO URLs...
@ -207,7 +227,7 @@ func route_forum(w http.ResponseWriter, r *http.Request, sfid string){
return
}
user, headerVars, ok := ForumSessionCheck(w,r,fid)
headerVars, ok := ForumSessionCheck(w,r,&user,fid)
if !ok {
return
}
@ -227,7 +247,7 @@ func route_forum(w http.ResponseWriter, r *http.Request, sfid string){
return
}
BuildWidgets("view_forum",&forum,&headerVars)
BuildWidgets("view_forum",forum,&headerVars,r)
// Calculate the offset
var offset int
@ -271,8 +291,8 @@ func route_forum(w http.ResponseWriter, r *http.Request, sfid string){
InternalError(err,w,r)
}
if hooks["trow_assign"] != nil {
topicItem = run_hook("trow_assign", topicItem).(TopicUser)
if hooks["forum_trow_assign"] != nil {
run_vhook("forum_trow_assign", &topicItem, &forum)
}
topicList = append(topicList, topicItem)
}
@ -284,6 +304,12 @@ func route_forum(w http.ResponseWriter, r *http.Request, sfid string){
rows.Close()
pi := ForumPage{forum.Name,user,headerVars,topicList,*forum,page,last_page,extData}
if pre_render_hooks["pre_render_view_forum"] != nil {
if run_pre_render_hook("pre_render_view_forum", w, r, &user, &pi) {
return
}
}
if template_forum_handle != nil {
template_forum_handle(pi,w)
} else {
@ -298,21 +324,33 @@ func route_forum(w http.ResponseWriter, r *http.Request, sfid string){
}
}
func route_forums(w http.ResponseWriter, r *http.Request){
user, headerVars, ok := SessionCheck(w,r)
func route_forums(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
return
}
BuildWidgets("forums",nil,&headerVars)
BuildWidgets("forums",nil,&headerVars,r)
var forumList []Forum
var err error
group := groups[user.Group]
//fmt.Println(group.CanSee)
for _, fid := range group.CanSee {
var forumList []Forum
var canSee []int
if user.Is_Super_Admin {
canSee, err = fstore.GetAllIDs()
if err != nil {
InternalError(err,w,r)
return
}
//fmt.Println("canSee",canSee)
} else {
group := groups[user.Group]
canSee = group.CanSee
//fmt.Println("group.CanSee",group.CanSee)
}
for _, fid := range canSee {
//fmt.Println(forums[fid])
var forum Forum = *fstore.DirtyGet(fid)
if forum.Active && forum.Name != "" {
if forum.Active && forum.Name != "" && forum.ParentID == 0 {
if forum.LastTopicID != 0 {
forum.LastTopicTime, err = relative_time(forum.LastTopicTime)
if err != nil {
@ -322,11 +360,20 @@ func route_forums(w http.ResponseWriter, r *http.Request){
forum.LastTopic = "None"
forum.LastTopicTime = ""
}
if hooks["forums_frow_assign"] != nil {
run_hook("forums_frow_assign", &forum)
}
forumList = append(forumList, forum)
}
}
pi := ForumsPage{"Forum List",user,headerVars,forumList,extData}
if pre_render_hooks["pre_render_forum_list"] != nil {
if run_pre_render_hook("pre_render_forum_list", w, r, &user, &pi) {
return
}
}
if template_forums_handle != nil {
template_forums_handle(pi,w)
} else {
@ -341,7 +388,7 @@ func route_forums(w http.ResponseWriter, r *http.Request){
}
}
func route_topic_id(w http.ResponseWriter, r *http.Request){
func route_topic_id(w http.ResponseWriter, r *http.Request, user User){
var err error
var page, offset int
var replyList []Reply
@ -371,7 +418,7 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
}
topic.Css = no_css_tmpl
user, headerVars, ok := ForumSessionCheck(w,r,topic.ParentID)
headerVars, ok := ForumSessionCheck(w,r,&user,topic.ParentID)
if !ok {
return
}
@ -381,7 +428,7 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
return
}
BuildWidgets("view_topic",&topic,&headerVars)
BuildWidgets("view_topic",&topic,&headerVars,r)
topic.Content = parse_message(topic.Content)
topic.ContentLines = strings.Count(topic.Content,"\n")
@ -500,8 +547,9 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
}
replyItem.Liked = false
// TO-DO: Rename this to topic_rrow_assign
if hooks["rrow_assign"] != nil {
replyItem = run_hook("rrow_assign", replyItem).(Reply)
run_hook("rrow_assign", &replyItem)
}
replyList = append(replyList, replyItem)
}
@ -513,6 +561,12 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
rows.Close()
tpage := TopicPage{topic.Title,user,headerVars,replyList,topic,page,last_page,extData}
if pre_render_hooks["pre_render_view_topic"] != nil {
if run_pre_render_hook("pre_render_view_topic", w, r, &user, &tpage) {
return
}
}
if template_topic_handle != nil {
template_topic_handle(tpage,w)
} else {
@ -527,8 +581,8 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
}
}
func route_profile(w http.ResponseWriter, r *http.Request){
user, headerVars, ok := SessionCheck(w,r)
func route_profile(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
return
}
@ -607,6 +661,8 @@ func route_profile(w http.ResponseWriter, r *http.Request){
replyLiked := false
replyLikeCount := 0
// TO-DO: Add a hook here
replyList = append(replyList, Reply{rid,puser.ID,replyContent,parse_message(replyContent),replyCreatedBy,name_to_slug(replyCreatedByName),replyCreatedByName,replyGroup,replyCreatedAt,replyLastEdit,replyLastEditBy,replyAvatar,replyCss,replyLines,replyTag,"","","",0,"",replyLiked,replyLikeCount,"",""})
}
err = rows.Err()
@ -616,6 +672,12 @@ func route_profile(w http.ResponseWriter, r *http.Request){
}
ppage := ProfilePage{puser.Name + "'s Profile",user,headerVars,replyList,*puser,extData}
if pre_render_hooks["pre_render_profile"] != nil {
if run_pre_render_hook("pre_render_profile", w, r, &user, &ppage) {
return
}
}
if template_profile_handle != nil {
template_profile_handle(ppage,w)
} else {
@ -626,7 +688,7 @@ func route_profile(w http.ResponseWriter, r *http.Request){
}
}
func route_topic_create(w http.ResponseWriter, r *http.Request, sfid string){
func route_topic_create(w http.ResponseWriter, r *http.Request, user User, sfid string){
var fid int
var err error
if sfid != "" {
@ -637,7 +699,7 @@ func route_topic_create(w http.ResponseWriter, r *http.Request, sfid string){
}
}
user, headerVars, ok := ForumSessionCheck(w,r,fid)
headerVars, ok := ForumSessionCheck(w,r,&user,fid)
if !ok {
return
}
@ -646,16 +708,56 @@ func route_topic_create(w http.ResponseWriter, r *http.Request, sfid string){
return
}
BuildWidgets("create_topic",nil,&headerVars,r)
// Lock this to the forum being linked?
// Should we always put it in strictmode when it's linked from another forum? Well, the user might end up changing their mind on what forum they want to post in and it would be a hassle, if they had to switch pages, even if it is a single click for many (exc. mobile)
var strictmode bool
if vhooks["topic_create_pre_loop"] != nil {
run_vhook("topic_create_pre_loop", w, r, fid, &headerVars, &user, &strictmode)
}
var forumList []Forum
group := groups[user.Group]
for _, fid := range group.CanSee {
forum := fstore.DirtyGet(fid)
var canSee []int
if user.Is_Super_Admin {
canSee, err = fstore.GetAllIDs()
if err != nil {
InternalError(err,w,r)
return
}
} else {
group := groups[user.Group]
canSee = group.CanSee
}
// TO-DO: plugin_superadmin needs to be able to override this loop. Skip flag on topic_create_pre_loop?
for _, ffid := range canSee {
// TO-DO: Surely, there's a better way of doing this. I've added it in for now to support plugin_socialgroups, but we really need to clean this up
if strictmode && ffid != fid {
continue
}
// Do a bulk forum fetch, just in case it's the SqlForumStore?
forum := fstore.DirtyGet(ffid)
if forum.Active && forum.Name != "" {
forumList = append(forumList, *forum)
fcopy := *forum
if hooks["topic_create_frow_assign"] != nil {
// TO-DO: Add the skip feature to all the other row based hooks?
if run_hook("topic_create_frow_assign", &fcopy).(bool) {
continue
}
}
forumList = append(forumList, fcopy)
}
}
ctpage := CreateTopicPage{"Create Topic",user,headerVars,forumList,fid,extData}
if pre_render_hooks["pre_render_create_topic"] != nil {
if run_pre_render_hook("pre_render_create_topic", w, r, &user, &ctpage) {
return
}
}
if template_create_topic_handle != nil {
template_create_topic_handle(ctpage,w)
} else {
@ -667,7 +769,7 @@ func route_topic_create(w http.ResponseWriter, r *http.Request, sfid string){
}
// POST functions. Authorised users only.
func route_create_topic(w http.ResponseWriter, r *http.Request) {
func route_topic_create_submit(w http.ResponseWriter, r *http.Request, user User) {
err := r.ParseForm()
if err != nil {
PreError("Bad Form",w,r)
@ -680,7 +782,7 @@ func route_create_topic(w http.ResponseWriter, r *http.Request) {
return
}
user, ok := SimpleForumSessionCheck(w,r,fid)
ok := SimpleForumSessionCheck(w,r,&user,fid)
if !ok {
return
}
@ -734,7 +836,7 @@ func route_create_topic(w http.ResponseWriter, r *http.Request) {
}
}
func route_create_reply(w http.ResponseWriter, r *http.Request) {
func route_create_reply(w http.ResponseWriter, r *http.Request, user User) {
err := r.ParseForm()
if err != nil {
PreError("Bad Form",w,r)
@ -755,7 +857,7 @@ func route_create_reply(w http.ResponseWriter, r *http.Request) {
return
}
user, ok := SimpleForumSessionCheck(w,r,topic.ParentID)
ok := SimpleForumSessionCheck(w,r,&user,topic.ParentID)
if !ok {
return
}
@ -829,7 +931,7 @@ func route_create_reply(w http.ResponseWriter, r *http.Request) {
}
}
func route_like_topic(w http.ResponseWriter, r *http.Request) {
func route_like_topic(w http.ResponseWriter, r *http.Request, user User) {
err := r.ParseForm()
if err != nil {
PreError("Bad Form",w,r)
@ -851,7 +953,7 @@ func route_like_topic(w http.ResponseWriter, r *http.Request) {
return
}
user, ok := SimpleForumSessionCheck(w,r,topic.ParentID)
ok := SimpleForumSessionCheck(w,r,&user,topic.ParentID)
if !ok {
return
}
@ -929,7 +1031,7 @@ func route_like_topic(w http.ResponseWriter, r *http.Request) {
http.Redirect(w,r,"/topic/" + strconv.Itoa(tid),http.StatusSeeOther)
}
func route_reply_like_submit(w http.ResponseWriter, r *http.Request) {
func route_reply_like_submit(w http.ResponseWriter, r *http.Request, user User) {
err := r.ParseForm()
if err != nil {
PreError("Bad Form",w,r)
@ -961,7 +1063,7 @@ func route_reply_like_submit(w http.ResponseWriter, r *http.Request) {
return
}
user, ok := SimpleForumSessionCheck(w,r,fid)
ok := SimpleForumSessionCheck(w,r,&user,fid)
if !ok {
return
}
@ -1029,11 +1131,7 @@ func route_reply_like_submit(w http.ResponseWriter, r *http.Request) {
http.Redirect(w,r,"/topic/" + strconv.Itoa(reply.ParentID),http.StatusSeeOther)
}
func route_profile_reply_create(w http.ResponseWriter, r *http.Request) {
user, ok := SimpleSessionCheck(w,r)
if !ok {
return
}
func route_profile_reply_create(w http.ResponseWriter, r *http.Request, user User) {
if !user.Loggedin || !user.Perms.CreateReply {
NoPermissions(w,r,user)
return
@ -1075,11 +1173,7 @@ func route_profile_reply_create(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/user/" + strconv.Itoa(uid), http.StatusSeeOther)
}
func route_report_submit(w http.ResponseWriter, r *http.Request, sitem_id string) {
user, ok := SimpleSessionCheck(w,r)
if !ok {
return
}
func route_report_submit(w http.ResponseWriter, r *http.Request, user User, sitem_id string) {
if !user.Loggedin {
LoginRequired(w,r,user)
return
@ -1216,8 +1310,8 @@ func route_report_submit(w http.ResponseWriter, r *http.Request, sitem_id string
http.Redirect(w,r,"/topic/" + strconv.FormatInt(lastId, 10), http.StatusSeeOther)
}
func route_account_own_edit_critical(w http.ResponseWriter, r *http.Request) {
user, headerVars, ok := SessionCheck(w,r)
func route_account_own_edit_critical(w http.ResponseWriter, r *http.Request, user User) {
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
return
}
@ -1225,12 +1319,18 @@ func route_account_own_edit_critical(w http.ResponseWriter, r *http.Request) {
LocalError("You need to login to edit your account.",w,r,user)
return
}
pi := Page{"Edit Password",user,headerVars,tList,nil}
if pre_render_hooks["pre_render_account_own_edit_critical"] != nil {
if run_pre_render_hook("pre_render_account_own_edit_critical", w, r, &user, &pi) {
return
}
}
templates.ExecuteTemplate(w,"account-own-edit.html", pi)
}
func route_account_own_edit_critical_submit(w http.ResponseWriter, r *http.Request) {
user, headerVars, ok := SessionCheck(w,r)
func route_account_own_edit_critical_submit(w http.ResponseWriter, r *http.Request, user User) {
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
return
}
@ -1259,9 +1359,8 @@ func route_account_own_edit_critical_submit(w http.ResponseWriter, r *http.Reque
return
}
current_password = current_password + salt
err = bcrypt.CompareHashAndPassword([]byte(real_password), []byte(current_password))
if err == bcrypt.ErrMismatchedHashAndPassword {
err = CheckPassword(real_password,current_password,salt)
if err == ErrMismatchedHashAndPassword {
LocalError("That's not the correct password.",w,r,user)
return
} else if err != nil {
@ -1279,11 +1378,16 @@ func route_account_own_edit_critical_submit(w http.ResponseWriter, r *http.Reque
headerVars.NoticeList = append(headerVars.NoticeList,"Your password was successfully updated")
pi := Page{"Edit Password",user,headerVars,tList,nil}
if pre_render_hooks["pre_render_account_own_edit_critical"] != nil {
if run_pre_render_hook("pre_render_account_own_edit_critical", w, r, &user, &pi) {
return
}
}
templates.ExecuteTemplate(w,"account-own-edit.html", pi)
}
func route_account_own_edit_avatar(w http.ResponseWriter, r *http.Request) {
user, headerVars, ok := SessionCheck(w,r)
func route_account_own_edit_avatar(w http.ResponseWriter, r *http.Request, user User) {
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
return
}
@ -1292,17 +1396,22 @@ func route_account_own_edit_avatar(w http.ResponseWriter, r *http.Request) {
return
}
pi := Page{"Edit Avatar",user,headerVars,tList,nil}
if pre_render_hooks["pre_render_account_own_edit_avatar"] != nil {
if run_pre_render_hook("pre_render_account_own_edit_avatar", w, r, &user, &pi) {
return
}
}
templates.ExecuteTemplate(w,"account-own-edit-avatar.html",pi)
}
func route_account_own_edit_avatar_submit(w http.ResponseWriter, r *http.Request) {
func route_account_own_edit_avatar_submit(w http.ResponseWriter, r *http.Request, user User) {
if r.ContentLength > int64(max_request_size) {
http.Error(w,"Request too large",http.StatusExpectationFailed)
return
}
r.Body = http.MaxBytesReader(w, r.Body, int64(max_request_size))
user, headerVars, ok := SessionCheck(w,r)
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
return
}
@ -1385,11 +1494,16 @@ func route_account_own_edit_avatar_submit(w http.ResponseWriter, r *http.Request
headerVars.NoticeList = append(headerVars.NoticeList, "Your avatar was successfully updated")
pi := Page{"Edit Avatar",user,headerVars,tList,nil}
if pre_render_hooks["pre_render_account_own_edit_avatar"] != nil {
if run_pre_render_hook("pre_render_account_own_edit_avatar", w, r, &user, &pi) {
return
}
}
templates.ExecuteTemplate(w,"account-own-edit-avatar.html", pi)
}
func route_account_own_edit_username(w http.ResponseWriter, r *http.Request) {
user, headerVars, ok := SessionCheck(w,r)
func route_account_own_edit_username(w http.ResponseWriter, r *http.Request, user User) {
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
return
}
@ -1398,11 +1512,16 @@ func route_account_own_edit_username(w http.ResponseWriter, r *http.Request) {
return
}
pi := Page{"Edit Username",user,headerVars,tList,user.Name}
if pre_render_hooks["pre_render_account_own_edit_username"] != nil {
if run_pre_render_hook("pre_render_account_own_edit_username", w, r, &user, &pi) {
return
}
}
templates.ExecuteTemplate(w,"account-own-edit-username.html",pi)
}
func route_account_own_edit_username_submit(w http.ResponseWriter, r *http.Request) {
user, headerVars, ok := SessionCheck(w,r)
func route_account_own_edit_username_submit(w http.ResponseWriter, r *http.Request, user User) {
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
return
}
@ -1432,11 +1551,16 @@ func route_account_own_edit_username_submit(w http.ResponseWriter, r *http.Reque
headerVars.NoticeList = append(headerVars.NoticeList,"Your username was successfully updated")
pi := Page{"Edit Username",user,headerVars,tList,nil}
if pre_render_hooks["pre_render_account_own_edit_username"] != nil {
if run_pre_render_hook("pre_render_account_own_edit_username", w, r, &user, &pi) {
return
}
}
templates.ExecuteTemplate(w,"account-own-edit-username.html", pi)
}
func route_account_own_edit_email(w http.ResponseWriter, r *http.Request) {
user, headerVars, ok := SessionCheck(w,r)
func route_account_own_edit_email(w http.ResponseWriter, r *http.Request, user User) {
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
return
}
@ -1482,11 +1606,16 @@ func route_account_own_edit_email(w http.ResponseWriter, r *http.Request) {
headerVars.NoticeList = append(headerVars.NoticeList,"The mail system is currently disabled.")
}
pi := Page{"Email Manager",user,headerVars,emailList,nil}
if pre_render_hooks["pre_render_account_own_edit_email"] != nil {
if run_pre_render_hook("pre_render_account_own_edit_email", w, r, &user, &pi) {
return
}
}
templates.ExecuteTemplate(w,"account-own-edit-email.html", pi)
}
func route_account_own_edit_email_token_submit(w http.ResponseWriter, r *http.Request) {
user, headerVars, ok := SessionCheck(w,r)
func route_account_own_edit_email_token_submit(w http.ResponseWriter, r *http.Request, user User) {
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
return
}
@ -1556,14 +1685,15 @@ func route_account_own_edit_email_token_submit(w http.ResponseWriter, r *http.Re
}
headerVars.NoticeList = append(headerVars.NoticeList,"Your email was successfully verified")
pi := Page{"Email Manager",user,headerVars,emailList,nil}
if pre_render_hooks["pre_render_account_own_edit_email"] != nil {
if run_pre_render_hook("pre_render_account_own_edit_email", w, r, &user, &pi) {
return
}
}
templates.ExecuteTemplate(w,"account-own-edit-email.html", pi)
}
func route_logout(w http.ResponseWriter, r *http.Request) {
user, ok := SimpleSessionCheck(w,r)
if !ok {
return
}
func route_logout(w http.ResponseWriter, r *http.Request, user User) {
if !user.Loggedin {
LocalError("You can't logout without logging in first.",w,r,user)
return
@ -1572,8 +1702,8 @@ func route_logout(w http.ResponseWriter, r *http.Request) {
http.Redirect(w,r, "/", http.StatusSeeOther)
}
func route_login(w http.ResponseWriter, r *http.Request) {
user, headerVars, ok := SessionCheck(w,r)
func route_login(w http.ResponseWriter, r *http.Request, user User) {
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
return
}
@ -1582,14 +1712,15 @@ func route_login(w http.ResponseWriter, r *http.Request) {
return
}
pi := Page{"Login",user,headerVars,tList,nil}
if pre_render_hooks["pre_render_login"] != nil {
if run_pre_render_hook("pre_render_login", w, r, &user, &pi) {
return
}
}
templates.ExecuteTemplate(w,"login.html",pi)
}
func route_login_submit(w http.ResponseWriter, r *http.Request) {
user, ok := SimpleSessionCheck(w,r)
if !ok {
return
}
func route_login_submit(w http.ResponseWriter, r *http.Request, user User) {
if user.Loggedin {
LocalError("You're already logged in.",w,r,user)
return
@ -1621,8 +1752,8 @@ func route_login_submit(w http.ResponseWriter, r *http.Request) {
http.Redirect(w,r,"/",http.StatusSeeOther)
}
func route_register(w http.ResponseWriter, r *http.Request) {
user, headerVars, ok := SessionCheck(w,r)
func route_register(w http.ResponseWriter, r *http.Request, user User) {
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
return
}
@ -1630,14 +1761,16 @@ func route_register(w http.ResponseWriter, r *http.Request) {
LocalError("You're already logged in.",w,r,user)
return
}
templates.ExecuteTemplate(w,"register.html",Page{"Registration",user,headerVars,tList,nil})
pi := Page{"Registration",user,headerVars,tList,nil}
if pre_render_hooks["pre_render_register"] != nil {
if run_pre_render_hook("pre_render_register", w, r, &user, &pi) {
return
}
}
templates.ExecuteTemplate(w,"register.html",pi)
}
func route_register_submit(w http.ResponseWriter, r *http.Request) {
user, ok := SimpleSessionCheck(w,r)
if !ok {
return
}
func route_register_submit(w http.ResponseWriter, r *http.Request, user User) {
err := r.ParseForm()
if err != nil {
LocalError("Bad Form",w,r,user)
@ -1734,9 +1867,10 @@ func route_register_submit(w http.ResponseWriter, r *http.Request) {
}
var phrase_login_alerts []byte = []byte(`{"msgs":[{"msg":"Login to see your alerts","path":"/accounts/login"}]}`)
func route_api(w http.ResponseWriter, r *http.Request) {
func route_api(w http.ResponseWriter, r *http.Request, user User) {
err := r.ParseForm()
format := r.FormValue("format")
// TO-DO: Change is_js from a string to a boolean value
var is_js string
if format == "json" {
is_js = "1"
@ -1748,11 +1882,6 @@ func route_api(w http.ResponseWriter, r *http.Request) {
return
}
user, ok := SimpleSessionCheck(w,r)
if !ok {
return
}
action := r.FormValue("action")
if action != "get" && action != "set" {
PreErrorJSQ("Invalid Action",w,r,is_js)

0
schema/mysql/inserts.sql Normal file
View File

View File

@ -0,0 +1,26 @@
CREATE TABLE `users` (
`uid` int not null AUTO_INCREMENT,
`name` varchar(100) not null,
`password` varchar(100) not null,
`salt` varchar(80) DEFAULT '' not null,
`group` int not null,
`active` boolean DEFAULT 0 not null,
`is_super_admin` boolean DEFAULT 0 not null,
`createdAt` datetime not null,
`lastActiveAt` datetime not null,
`session` varchar(200) DEFAULT '' not null,
`last_ip` varchar(200) DEFAULT '0.0.0.0.0' not null,
`email` varchar(200) DEFAULT '' not null,
`avatar` varchar(100) DEFAULT '' not null,
`message` text not null,
`url_prefix` varchar(20) DEFAULT '' not null,
`url_name` varchar(100) DEFAULT '' not null,
`level` smallint DEFAULT 0 not null,
`score` int DEFAULT 0 not null,
`posts` int DEFAULT 0 not null,
`bigposts` int DEFAULT 0 not null,
`megaposts` int DEFAULT 0 not null,
`topics` int DEFAULT 0 not null,
primary key(`uid`),
unique(`name`)
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;

0
schema/pgsql/inserts.sql Normal file
View File

View File

@ -0,0 +1,26 @@
CREATE TABLE `users` (
`uid` serial not null,
`name` varchar (100) not null,
`password` varchar (100) not null,
`salt` varchar (80) DEFAULT '' not null,
`group` int not null,
`active` boolean DEFAULT 0 not null,
`is_super_admin` boolean DEFAULT 0 not null,
`createdAt` timestamp not null,
`lastActiveAt` timestamp not null,
`session` varchar (200) DEFAULT '' not null,
`last_ip` varchar (200) DEFAULT '0.0.0.0.0' not null,
`email` varchar (200) DEFAULT '' not null,
`avatar` varchar (100) DEFAULT '' not null,
`message` text DEFAULT '' not null,
`url_prefix` varchar (20) DEFAULT '' not null,
`url_name` varchar (100) DEFAULT '' not null,
`level` smallint DEFAULT 0 not null,
`score` int DEFAULT 0 not null,
`posts` int DEFAULT 0 not null,
`bigposts` int DEFAULT 0 not null,
`megaposts` int DEFAULT 0 not null,
`topics` int DEFAULT 0 not null,
primary key(`uid`),
unique(`name`)
);

View File

@ -9,9 +9,9 @@
<div class="sgBackdrop">
<div class="miniMenu">
<div class="menuItem"><a href="#">Test Group</a></div>
<div class="menuItem"><a href="/group/{{.SocialGroup.ID}}">{{.SocialGroup.Name}}</a></div>
<div class="menuItem"><a href="#">About</a></div>
<div class="menuItem"><a href="#">Members</a></div>
<div class="menuItem"><a href="/group/members/{{.SocialGroup.ID}}">Members</a></div>
<div class="menuItem rightMenu"><a href="#">Edit</a></div>
<div class="menuItem rightMenu"><a href="/group/join/{{.SocialGroup.ID}}">Join</a></div>
</div>

View File

@ -9,10 +9,11 @@
<div class="sgBackdrop">
<div class="miniMenu">
<div class="menuItem"><a href="#">Test Group</a></div>
<div class="menuItem"><a href="/group/{{.SocialGroup.ID}}">{{.SocialGroup.Name}}</a></div>
<div class="menuItem"><a href="#">About</a></div>
<div class="menuItem"><a href="#">Members</a></div>
<div class="menuItem"><a href="/group/members/{{.SocialGroup.ID}}">Members</a></div>
<div class="menuItem rightMenu"><a href="#">Edit</a></div>
<div class="menuItem rightMenu"><a href="/topics/create/{{.Forum.ID}}">Reply</a></div>
<div class="menuItem rightMenu"><a href="/group/join/{{.SocialGroup.ID}}">Join</a></div>
</div>
<div style="clear: both;"></div>

View File

@ -151,7 +151,6 @@ li:hover {
hr { color: silver; border: 1px solid silver; }
/* I HATE CSS for being so incompetently designed that I have to declare this for THREE different elements rather than just having a statement go back up the tree. What on earth is the W3C doing?! */
.rowhead .rowitem, .colstack_head .rowitem, .opthead .rowitem {
border-top: none;
font-weight: bold;

View File

@ -139,7 +139,6 @@ li:hover {
hr { color: silver; border: 1px solid silver; }
/* I HATE CSS for being so incompetently designed that I have to declare this for THREE different elements rather than just having a statement go back up the tree. What on earth is the W3C doing?! */
.rowhead .rowitem, .opthead .rowitem, .colstack_head .rowitem {
border-top: none;
font-weight: bold;

View File

@ -19,6 +19,13 @@ if %errorlevel% neq 0 (
exit /b %errorlevel%
)
echo Updating /x/system/windows (dependency for gopsutil)
go get -u golang.org/x/sys/windows
if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
echo Updating wmi (dependency for gopsutil)
go get -u github.com/StackExchange/wmi
if %errorlevel% neq 0 (
@ -41,4 +48,4 @@ if %errorlevel% neq 0 (
)
echo The dependencies were successfully updated
pause
pause

30
user.go
View File

@ -12,13 +12,16 @@ import (
)
var guest_user User = User{ID:0,Group:6,Perms:GuestPerms}
var PreRoute func(http.ResponseWriter, *http.Request) (User,bool) = _pre_route
var PanelSessionCheck func(http.ResponseWriter, *http.Request, *User) (HeaderVars,bool) = _panel_session_check
var SimplePanelSessionCheck func(http.ResponseWriter, *http.Request, *User) bool = _simple_panel_session_check
var SimpleForumSessionCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (success bool) = _simple_forum_session_check
var ForumSessionCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars HeaderVars, success bool) = _forum_session_check
var SessionCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerVars HeaderVars, success bool) = _session_check
var CheckPassword func(real_password string, password string, salt string) (err error) = BcryptCheckPassword
var GeneratePassword func(password string) (hashed_password string, salt string, err error) = BcryptGeneratePassword
type User struct
{
@ -60,19 +63,35 @@ func BcryptCheckPassword(real_password string, password string, salt string) (er
return bcrypt.CompareHashAndPassword([]byte(real_password), []byte(password + salt))
}
func SetPassword(uid int, password string) (error) {
salt, err := GenerateSafeString(saltLength)
// Investigate. Do we need the extra salt?
func BcryptGeneratePassword(password string) (hashed_password string, salt string, err error) {
salt, err = GenerateSafeString(saltLength)
if err != nil {
return err
return "", "", err
}
password = password + salt
hashed_password, err = BcryptGeneratePasswordNoSalt(password)
if err != nil {
return "", "", err
}
return hashed_password, salt, nil
}
func BcryptGeneratePasswordNoSalt(password string) (hash string, err error) {
hashed_password, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hashed_password), nil
}
func SetPassword(uid int, password string) error {
hashed_password, salt, err := GeneratePassword(password)
if err != nil {
return err
}
_, err = set_password_stmt.Exec(string(hashed_password), salt, uid)
_, err = set_password_stmt.Exec(hashed_password, salt, uid)
if err != nil {
return err
}
@ -120,6 +139,7 @@ func _simple_forum_session_check(w http.ResponseWriter, r *http.Request, user *U
PreError("The target forum doesn't exist.",w,r)
return false
}
success = true
// Is there a better way of doing the skip AND the success flag on this hook like multiple returns?
if vhooks["simple_forum_check_pre_perms"] != nil {