diff --git a/README.md b/README.md index c7aadffa..d854f7f3 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ -# Grosolo +# Gosora A super fast forum software written in Go. -The initial code-base was forked from one of my side projects, and converted from the web framework it was using. +The initial code-base was forked from one of my side projects, but has now gone far beyond that. + +Discord Server: https://discord.gg/eyYvtTf # Features @@ -16,6 +18,8 @@ In-memory static file, forum and group caches. A profile system including profile comments and moderation tools for the profile owner. +A plugin system. + # Dependencies @@ -46,10 +50,32 @@ Add -u after go get to update those libraries, if you've already got them instal # Run the program -go run errors.go main.go pages.go reply.go routes.go topic.go user.go utils.go forum.go group.go files.go config.go +*Linux* + +cd to the directory / folder the code is in. + +go build + +./gosora + + +*Windows* + +Open up cmd.exe + +cd to the directory / folder the code is in. E.g. cd /Users/Blah/Documents/gosora + +go build + +./gosora.exe + Alternatively, you could run the run.bat batch file on Windows. +We're also looking into ways to distribute ready made executables for Windows. While this is not a complicated endeavour, the configuration settings currently get built with the rest of the program for speed, and we will likely have to change this. + +With the introduction of the new settings system, we will begin moving some of the less critical settings out of the configuration file, and will likely have a config.xml or config.ini in the future to store the critical settings in. + # TO-DO @@ -64,12 +90,8 @@ Add emails as a requirement for registration and add a simple anti-spam measure. Add an alert system. -Add a report feature. - Add a complex permissions system. -Add a settings system. - Add a plugin system. Tweak the CSS to make it responsive. @@ -77,3 +99,5 @@ Tweak the CSS to make it responsive. Nest the moderation routes to possibly speed routing up a little...? Add a friend system. + +Add more administration features. diff --git a/config.go b/config.go index d695304f..09c48462 100644 --- a/config.go +++ b/config.go @@ -4,7 +4,7 @@ package main var dbhost = "127.0.0.1" var dbuser = "root" var dbpassword = "password" -var dbname = "grosolo" +var dbname = "gosora" var dbport = "3306" // You probably won't need to change this // Limiters diff --git a/data.sql b/data.sql index 6c536d57..1cd09579 100644 --- a/data.sql +++ b/data.sql @@ -53,6 +53,7 @@ CREATE TABLE `topics`( `is_closed` tinyint DEFAULT 0 not null, `sticky` tinyint DEFAULT 0 not null, `parentID` int DEFAULT 1 not null, + `data` varchar(200) DEFAULT '' not null, primary key(`tid`) ) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; @@ -87,6 +88,12 @@ CREATE TABLE `settings`( unique(`name`) ); +CREATE TABLE `plugins`( + `uname` varchar(200) not null, + `active` tinyint DEFAULT 0 not null, + unique(`uname`) +); + INSERT INTO settings(`name`,`content`,`type`) VALUES ('url_tags','1','bool'); INSERT INTO users(`name`,`group`,`is_super_admin`,`createdAt`,`lastActiveAt`,`message`) diff --git a/extend.go b/extend.go new file mode 100644 index 00000000..0db39266 --- /dev/null +++ b/extend.go @@ -0,0 +1,25 @@ +/* Copyright Azareal 2016 - 2017 */ +package main + +var plugins map[string]Plugin = make(map[string]Plugin) +var hooks map[string]func(interface{})interface{} = make(map[string]func(interface{})interface{}) + +type Plugin struct +{ + UName string + Name string + Author string + URL string + Settings string + Active bool + Type string + Init func() +} + +func add_hook(name string, handler func(interface{})interface{}) { + hooks[name] = handler +} + +func run_hook(name string, data interface{}) interface{} { + return hooks[name](data) +} \ No newline at end of file diff --git a/extend/filler.txt b/extend/filler.txt new file mode 100644 index 00000000..20e14b1e --- /dev/null +++ b/extend/filler.txt @@ -0,0 +1 @@ +This file is here so that Git will include this folder in the repository. \ No newline at end of file diff --git a/grosolo-linux b/gosora-linux similarity index 50% rename from grosolo-linux rename to gosora-linux index a1cc146d..28ccc10f 100644 --- a/grosolo-linux +++ b/gosora-linux @@ -1,2 +1,2 @@ go build -./Grosolo \ No newline at end of file +./Gosora \ No newline at end of file diff --git a/grosolo.exe b/gosora.exe similarity index 60% rename from grosolo.exe rename to gosora.exe index c546ad92..c6a56616 100644 Binary files a/grosolo.exe and b/gosora.exe differ diff --git a/images/report.PNG b/images/report.PNG new file mode 100644 index 00000000..02ac5eb4 Binary files /dev/null and b/images/report.PNG differ diff --git a/main.go b/main.go index 6dd13d13..5dc4730b 100644 --- a/main.go +++ b/main.go @@ -1,8 +1,10 @@ +/* Copyright Azareal 2016 - 2017 */ package main import ( "net/http" "log" + //"fmt" "mime" "strings" "path/filepath" @@ -56,6 +58,17 @@ func main(){ } external_sites["YT"] = "https://www.youtube.com/" + hooks["trow_assign"] = nil + hooks["rrow_assign"] = nil + //fmt.Println(plugins) + + for name, body := range plugins { + log.Print("Added plugin " + name) + if body.Active { + log.Print("Initialised plugin " + name) + plugins[name].Init() + } + } // In a directory to stop it clashing with the other paths http.HandleFunc("/static/", route_static) @@ -77,6 +90,7 @@ func main(){ //http.HandleFunc("/reply/delete/", route_reply_delete) http.HandleFunc("/reply/edit/submit/", route_reply_edit_submit) http.HandleFunc("/reply/delete/submit/", route_reply_delete_submit) + http.HandleFunc("/report/submit/", route_report_submit) http.HandleFunc("/topic/edit/submit/", route_edit_topic) http.HandleFunc("/topic/delete/submit/", route_delete_topic) http.HandleFunc("/topic/stick/submit/", route_stick_topic) @@ -119,6 +133,9 @@ func main(){ http.HandleFunc("/panel/settings/", route_panel_settings) http.HandleFunc("/panel/settings/edit/", route_panel_setting) http.HandleFunc("/panel/settings/edit/submit/", route_panel_setting_edit) + http.HandleFunc("/panel/plugins/", route_panel_plugins) + http.HandleFunc("/panel/plugins/activate/", route_panel_plugins_activate) + //http.HandleFunc("/panel/plugins/deactivate/", route_panel_plugins_deactivate) http.HandleFunc("/", default_route) diff --git a/mod_routes.go b/mod_routes.go index 56a93eea..38f1d344 100644 --- a/mod_routes.go +++ b/mod_routes.go @@ -694,4 +694,66 @@ func route_panel_setting_edit(w http.ResponseWriter, r *http.Request) { return } http.Redirect(w,r,"/panel/settings/",http.StatusSeeOther) +} + +func route_panel_plugins(w http.ResponseWriter, r *http.Request){ + user := SessionCheck(w,r) + if !user.Is_Admin { + NoPermissions(w,r,user) + return + } + + var pluginList map[int]interface{} = make(map[int]interface{}) + currentID := 0 + + for _, plugin := range plugins { + pluginList[currentID] = plugin + currentID++ + } + + pi := Page{"Plugin Manager","panel-plugins",user,pluginList,0} + templates.ExecuteTemplate(w,"panel-plugins.html", pi) +} + +func route_panel_plugins_activate(w http.ResponseWriter, r *http.Request){ + user := SessionCheck(w,r) + if !user.Is_Admin { + NoPermissions(w,r,user) + return + } + + uname := r.URL.Path[len("/panel/plugins/activate/"):] + + plugin, ok := plugins[uname] + if !ok { + LocalError("The plugin isn't registered in the system",w,r,user) + return + } + + var active bool + err := db.QueryRow("SELECT active from plugins where uname = ?", uname).Scan(&active) + if err != nil && err != sql.ErrNoRows { + InternalError(err,w,r,user) + return + } + + has_plugin := err != sql.ErrNoRows + if has_plugin { + if active { + LocalError("The plugin is already active",w,r,user) + return + } + } else { + _, err := add_plugin_stmt.Exec(uname,1) + if err != nil { + InternalError(err,w,r,user) + return + } + } + + plugin.Active = true + plugins[uname] = plugin + plugins[uname].Init() + + http.Redirect(w,r,"/panel/plugins/",http.StatusSeeOther) } \ No newline at end of file diff --git a/mysql.go b/mysql.go index 406668f1..a99f9cb1 100644 --- a/mysql.go +++ b/mysql.go @@ -1,3 +1,4 @@ +/* Copyright Azareal 2016 - 2017 */ package main import "database/sql" @@ -8,6 +9,7 @@ import "log" var db *sql.DB var get_session_stmt *sql.Stmt var create_topic_stmt *sql.Stmt +var create_report_stmt *sql.Stmt var create_reply_stmt *sql.Stmt var update_forum_cache_stmt *sql.Stmt var edit_topic_stmt *sql.Stmt @@ -34,6 +36,7 @@ var create_forum_stmt *sql.Stmt var delete_forum_stmt *sql.Stmt var update_forum_stmt *sql.Stmt var update_setting_stmt *sql.Stmt +var add_plugin_stmt *sql.Stmt func init_database(err error) { if(dbpassword != ""){ @@ -62,6 +65,12 @@ func init_database(err error) { log.Fatal(err) } + log.Print("Preparing create_report statement.") + create_report_stmt, err = db.Prepare("INSERT INTO topics(title,content,parsed_content,createdAt,createdBy,data,parentID) VALUES(?,?,?,NOW(),?,?,-1)") + if err != nil { + log.Fatal(err) + } + log.Print("Preparing create_reply statement.") create_reply_stmt, err = db.Prepare("INSERT INTO replies(tid,content,parsed_content,createdAt,createdBy) VALUES(?,?,?,NOW(),?)") if err != nil { @@ -215,6 +224,12 @@ func init_database(err error) { log.Fatal(err) } + log.Print("Preparing add_plugin statement.") + add_plugin_stmt, err = db.Prepare("INSERT INTO plugins(uname,active) VALUES(?,?)") + if err != nil { + log.Fatal(err) + } + log.Print("Loading the usergroups.") rows, err := db.Query("SELECT gid,name,permissions,is_mod,is_admin,is_banned,tag FROM users_groups") if err != nil { @@ -295,4 +310,27 @@ func init_database(err error) { if err != nil { log.Fatal(err) } + + log.Print("Loading the plugins.") + rows, err = db.Query("SELECT uname, active FROM plugins") + if err != nil { + log.Fatal(err) + } + defer rows.Close() + + var uname string + var active bool + for rows.Next() { + err := rows.Scan(&uname, &active) + if err != nil { + log.Fatal(err) + } + plugin := plugins[uname] + plugin.Active = active + plugins[uname] = plugin + } + err = rows.Err() + if err != nil { + log.Fatal(err) + } } diff --git a/plugin_helloworld.go b/plugin_helloworld.go new file mode 100644 index 00000000..e18d1ee2 --- /dev/null +++ b/plugin_helloworld.go @@ -0,0 +1,19 @@ +package main +import "html/template" + +func init() { + plugins["helloworld"] = Plugin{"helloworld","Hello World","Azareal","http://github.com/Azareal","",false,"",init_helloworld} +} + +// init_helloworld is separate from init() as we don't want the plugin to run if the plugin is disabled +func init_helloworld() { + add_hook("rrow_assign", helloworld_reply) +} + +func helloworld_reply(data interface{}) interface{} { + reply := data.(Reply) + reply.Content = "Hello World!" + reply.ContentHtml = template.HTML("Hello World!") + reply.Tag = "Automated" + return reply +} \ No newline at end of file diff --git a/plugin_skeleton.go b/plugin_skeleton.go new file mode 100644 index 00000000..b2f78ced --- /dev/null +++ b/plugin_skeleton.go @@ -0,0 +1,17 @@ +package main + +func init() { + /* + The UName field should match the name in the URL minus plugin_ and the file extension. The same name as the map index. Please choose a unique name which won't clash with any other plugins. + The Name field is for the friendly name of the plugin shown to the end-user. + The Author field is the author of this plugin. The one who created it. + The URL field is for the URL pointing to the location where you can download this plugin. + The Settings field points to the route for managing the settings for this plugin. Coming soon. + The Active field should always be set to false in the init() function of a plugin. It's used internally by the software to determine whether an admin has enabled a plugin or not and whether to run it. This will be overwritten by the user's preference. + The Type field is for the type of the plugin. This gets changed to "go" automatically and we would suggest leaving "". + The Init field is for the initialisation handler which is called by the software to run this plugin. This expects a function. You should add your hooks, init logic, initial queries, etc. in said function. + */ + plugins["skeleton"] = Plugin{"skeleton","Skeleton","Azareal","","",false,"",init_test} +} + +func init_test() {} diff --git a/reply.go b/reply.go index 82aec635..f7c5c806 100644 --- a/reply.go +++ b/reply.go @@ -1,3 +1,4 @@ +/* Copyright Azareal 2016 - 2017 */ package main import "html/template" diff --git a/routes.go b/routes.go index 47f4469f..2c839734 100644 --- a/routes.go +++ b/routes.go @@ -1,3 +1,4 @@ +/* Copyright Azareal 2016 - 2017 */ package main import "errors" @@ -110,6 +111,10 @@ func route_topics(w http.ResponseWriter, r *http.Request){ } topicList[currentID] = TopicUser{tid,title,content,createdBy,is_closed,sticky, createdAt,parentID,status,name,avatar,"",0,"","","",""} + + if hooks["trow_assign"] != nil { + topicList[currentID] = run_hook("trow_assign", topicList[currentID]) + } currentID++ } err = rows.Err() @@ -193,6 +198,10 @@ func route_forum(w http.ResponseWriter, r *http.Request){ } topicList[currentID] = TopicUser{tid,title,content,createdBy,is_closed,sticky,createdAt,parentID,status,name,avatar,"",0,"","","",""} + + if hooks["trow_assign"] != nil { + topicList[currentID] = run_hook("trow_assign", topicList[currentID]) + } currentID++ } err = rows.Err() @@ -364,6 +373,10 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){ } replyList[currentID] = Reply{rid,topic.ID,replyContent,template.HTML(parse_message(replyContent)),replyCreatedBy,replyCreatedByName,replyCreatedAt,replyLastEdit,replyLastEditBy,replyAvatar,replyCss,replyLines,replyTag,replyURL,replyURLPrefix,replyURLName} + + if hooks["rrow_assign"] != nil { + replyList[currentID] = run_hook("rrow_assign", replyList[currentID]) + } currentID++ } err = rows.Err() @@ -692,6 +705,128 @@ func route_profile_reply_create(w http.ResponseWriter, r *http.Request) { } } +func route_report_submit(w http.ResponseWriter, r *http.Request) { + user := SessionCheck(w,r) + if !user.Loggedin { + LoginRequired(w,r,user) + return + } + if user.Is_Banned { + Banned(w,r,user) + return + } + + err := r.ParseForm() + if err != nil { + LocalError("Bad Form", w, r, user) + return + } + + if r.FormValue("session") != user.Session { + SecurityError(w,r,user) + return + } + item_id, err := strconv.Atoi(r.URL.Path[len("/report/submit/"):]) + if err != nil { + LocalError("Bad ID", w, r, user) + return + } + + item_type := r.FormValue("type") + success := 1 + + var tid int + var title string + var content string + var data string + if item_type == "reply" { + err = db.QueryRow("select tid, content from replies where rid = ?", item_id).Scan(&tid, &content) + if err == sql.ErrNoRows { + LocalError("We were unable to find the reported post", w, r, user) + return + } else if err != nil { + InternalError(err,w,r,user) + return + } + + err = db.QueryRow("select title, data from topics where tid = ?", tid).Scan(&title,&data) + if err == sql.ErrNoRows { + LocalError("We were unable to find the topic which the reported post is supposed to be in", w, r, user) + return + } else if err != nil { + InternalError(err,w,r,user) + return + } + content = content + "

Original Post: " + title + "" + } else if item_type == "topic" { + err = db.QueryRow("select title, content from topics where tid = ?", item_id).Scan(&title,&content) + if err == sql.ErrNoRows { + NotFound(w,r,user) + return + } else if err != nil { + InternalError(err,w,r,user) + return + } + content = content + "

Original Post: " + title + "" + } else { + // Don't try to guess the type + LocalError("Unknown type", w, r, user) + return + } + + var count int + rows, err := db.Query("select count(*) as count from topics where data = ? and data != '' and parentID = -1", item_type + "_" + strconv.Itoa(item_id)) + if err != nil && err != sql.ErrNoRows { + InternalError(err,w,r,user) + return + } + + for rows.Next() { + err = rows.Scan(&count) + if err != nil { + InternalError(err,w,r,user) + return + } + } + + if count != 0 { + LocalError("Someone has already reported this!", w, r, user) + return + } + + title = "Report: " + title + res, err := create_report_stmt.Exec(title,content,content,user.ID,item_type + "_" + strconv.Itoa(item_id)) + if err != nil { + log.Print(err) + success = 0 + } + + lastId, err := res.LastInsertId() + if err != nil { + log.Print(err) + success = 0 + } + + _, err = update_forum_cache_stmt.Exec(title, lastId, user.Name, user.ID, 1) + if err != nil { + InternalError(err,w,r,user) + return + } + + if success != 1 { + errmsg := "Unable to create the report" + pi := Page{"Error","error",user,tList,errmsg} + + var b bytes.Buffer + templates.ExecuteTemplate(&b,"error.html", pi) + errpage := b.String() + w.WriteHeader(500) + fmt.Fprintln(w,errpage) + } else { + http.Redirect(w, r, "/topic/" + strconv.FormatInt(lastId, 10), http.StatusSeeOther) + } +} + func route_account_own_edit_critical(w http.ResponseWriter, r *http.Request) { user := SessionCheck(w,r) if !user.Loggedin { diff --git a/run.bat b/run.bat index 97bf2c4c..d939fc2a 100644 --- a/run.bat +++ b/run.bat @@ -1,3 +1,3 @@ go build -grosolo.exe +gosora.exe pause \ No newline at end of file diff --git a/templates/panel-forums.html b/templates/panel-forums.html index 7e89f3b0..a744e7a2 100644 --- a/templates/panel-forums.html +++ b/templates/panel-forums.html @@ -3,6 +3,8 @@
Control Panel
Forums
Settings
+
Plugins
+
Coming Soon
Coming Soon
Coming Soon
Reports
diff --git a/templates/panel-plugins.html b/templates/panel-plugins.html new file mode 100644 index 00000000..3fb71e7e --- /dev/null +++ b/templates/panel-plugins.html @@ -0,0 +1,28 @@ +{{template "header.html" . }} +
+
Control Panel
+
Forums
+
Settings
+
Plugins
+
Coming Soon
+
Coming Soon
+
Coming Soon
+
Reports
+
+
+
Plugins
+
+
+ {{range .ItemList}} +
+ {{.Name}}
+ Author: {{.Author}} + + {{if .Settings}}Settings{{end}} + {{if .Active}}Deactivate + {{else}}Activate{{end}} + +
+ {{end}} +
+{{template "footer.html" . }} \ No newline at end of file diff --git a/templates/panel-setting.html b/templates/panel-setting.html index 0d727b49..8dae1e98 100644 --- a/templates/panel-setting.html +++ b/templates/panel-setting.html @@ -3,6 +3,8 @@
Control Panel
Forums
Settings
+
Plugins
+
Coming Soon
Coming Soon
Coming Soon
Reports
diff --git a/templates/panel-settings.html b/templates/panel-settings.html index ef396d4a..6f249bab 100644 --- a/templates/panel-settings.html +++ b/templates/panel-settings.html @@ -3,6 +3,8 @@
Control Panel
Forums
Settings
+
Plugins
+
Coming Soon
Coming Soon
Coming Soon
Reports
diff --git a/templates/topic.html b/templates/topic.html index 5bc410fa..0a6f4c96 100644 --- a/templates/topic.html +++ b/templates/topic.html @@ -3,20 +3,21 @@
{{.Something.Title}} - {{.Something.Status}} + {{.Something.Status}} + Status {{if .CurrentUser.Is_Mod}} - Edit + Edit Delete {{ if .Something.Sticky }}Unpin{{else}}Pin{{end}} - {{end}} - Report + Report
@@ -38,7 +39,7 @@ {{$element.CreatedByName}} {{if $.CurrentUser.Is_Mod}} {{end}} - + {{if $element.Tag}}{{$element.Tag}}{{else if $element.URLName}}{{$element.URLName}} {{$element.URLPrefix}}{{end}} {{end}}