Added the forum cache. The rest of the code now uses it instead than firing queries on every request.

Added the ability for admins to delete forums.
Added the ability for admins to edit forums.
Added the Uncategorised Forum for topics with an ID of 0. Possibly for deleted forums in the future?
Added the uncategorised forum visibility switch.
Creating forums now has an anti-CSRF session check. The same is true with the newly implemented forum deletion and modification features.
Cleaned up some of the error code.
This commit is contained in:
Azareal 2016-12-06 10:26:48 +00:00
parent 84cbb3a5d5
commit 30ecdf8d93
14 changed files with 281 additions and 96 deletions

View File

@ -1 +1,2 @@
go build go build
pause

View File

@ -13,3 +13,4 @@ var max_request_size = 5 * megabyte
// Misc // Misc
var default_route = route_topics var default_route = route_topics
var staff_css = "background-color: #ffeaff;background-position: left;" var staff_css = "background-color: #ffeaff;background-position: left;"
var uncategorised_forum_visible = true

View File

@ -120,6 +120,26 @@ func LoginRequiredJSQ(w http.ResponseWriter, r *http.Request, user User, is_js s
} }
} }
func SecurityError(w http.ResponseWriter, r *http.Request, user User) {
errmsg := "There was a security issue with your request."
pi := Page{"Security Error","error",user,tList,errmsg}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
errpage := b.String()
w.WriteHeader(403)
fmt.Fprintln(w,errpage)
}
func NotFound(w http.ResponseWriter, r *http.Request, user User) {
errmsg := "The requested page doesn't exist."
pi := Page{"Not Found","error",user,tList,errmsg}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
errpage := b.String()
w.WriteHeader(404)
fmt.Fprintln(w,errpage)
}
func CustomErrorJSQ(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User, is_js string) { func CustomErrorJSQ(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User, is_js string) {
if is_js == "0" { if is_js == "0" {
pi := Page{errtitle,"error",user,tList,errmsg} pi := Page{errtitle,"error",user,tList,errmsg}

View File

@ -4,6 +4,7 @@ type Forum struct
{ {
ID int ID int
Name string Name string
Active bool
LastTopic string LastTopic string
LastTopicID int LastTopicID int
LastReplyer string LastReplyer string

2
grosolo-linux Normal file
View File

@ -0,0 +1,2 @@
go build
./Grosolo

Binary file not shown.

BIN
grosolo.exe~ Normal file

Binary file not shown.

56
main.go
View File

@ -42,12 +42,15 @@ var register_stmt *sql.Stmt
var username_exists_stmt *sql.Stmt var username_exists_stmt *sql.Stmt
var create_forum_stmt *sql.Stmt var create_forum_stmt *sql.Stmt
var delete_forum_stmt *sql.Stmt
var update_forum_stmt *sql.Stmt
var custom_pages map[string]string = make(map[string]string) var custom_pages map[string]string = make(map[string]string)
var templates = template.Must(template.ParseGlob("templates/*")) var templates = template.Must(template.ParseGlob("templates/*"))
var no_css_tmpl = template.CSS("") var no_css_tmpl = template.CSS("")
var staff_css_tmpl = template.CSS(staff_css) var staff_css_tmpl = template.CSS(staff_css)
var groups map[int]Group = make(map[int]Group) var groups map[int]Group = make(map[int]Group)
var forums map[int]Forum = make(map[int]Forum)
var static_files map[string]SFile = make(map[string]SFile) var static_files map[string]SFile = make(map[string]SFile)
func init_database(err error) { func init_database(err error) {
@ -188,8 +191,20 @@ func init_database(err error) {
log.Fatal(err) log.Fatal(err)
} }
log.Print("Preparing delete_forum statement.")
delete_forum_stmt, err = db.Prepare("DELETE FROM forums WHERE fid = ?")
if err != nil {
log.Fatal(err)
}
log.Print("Preparing update_forum statement.")
update_forum_stmt, err = db.Prepare("UPDATE forums SET name = ? WHERE fid = ?")
if err != nil {
log.Fatal(err)
}
log.Print("Loading the usergroups.") log.Print("Loading the usergroups.")
rows, err := db.Query("select gid,name,permissions,is_admin,is_banned from users_groups") rows, err := db.Query("SELECT gid,name,permissions,is_admin,is_banned FROM users_groups")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -207,6 +222,42 @@ func init_database(err error) {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
log.Print("Loading the forums.")
rows, err = db.Query("SELECT fid, name, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime FROM forums")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
forum := Forum{0,"",true,"",0,"",0,""}
err := rows.Scan(&forum.ID, &forum.Name, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime)
if err != nil {
log.Fatal(err)
}
if forum.LastTopicID != 0 {
forum.LastTopicTime, err = relative_time(forum.LastTopicTime)
if err != nil {
log.Fatal(err)
}
} else {
forum.LastTopic = "None"
forum.LastTopicTime = ""
}
forums[forum.ID] = forum
}
err = rows.Err()
if err != nil {
log.Fatal(err)
}
log.Print("Adding the uncategorised forum")
forums[0] = Forum{0,"Uncategorised",uncategorised_forum_visible,"",0,"",0,""}
log.Print("Adding the reports forum")
forums[-1] = Forum{-1,"Reports",false,"",0,"",0,""}
} }
func main(){ func main(){
@ -288,6 +339,9 @@ func main(){
// Admin // Admin
http.HandleFunc("/panel/forums/", route_panel_forums) http.HandleFunc("/panel/forums/", route_panel_forums)
http.HandleFunc("/panel/forums/create/", route_panel_forums_create_submit) http.HandleFunc("/panel/forums/create/", route_panel_forums_create_submit)
http.HandleFunc("/panel/forums/delete/", route_panel_forums_delete)
http.HandleFunc("/panel/forums/delete/submit/", route_panel_forums_delete_submit)
http.HandleFunc("/panel/forums/edit/submit/", route_panel_forums_edit_submit)
http.HandleFunc("/", default_route) http.HandleFunc("/", default_route)

View File

@ -21,6 +21,12 @@ type PageSimple struct
Something interface{} Something interface{}
} }
type AreYouSure struct
{
URL string
Message string
}
func add_custom_page(path string, f os.FileInfo, err error) error { func add_custom_page(path string, f os.FileInfo, err error) error {
if err != nil { if err != nil {
return err return err

View File

@ -83,4 +83,30 @@ $(document).ready(function(){
}); });
}); });
}); });
$(".edit_field").click(function(event)
{
event.preventDefault();
var block_parent = $(this).closest('.editable_parent');
var block = block_parent.find('.editable_block').eq(0);
block.html("<input name='edit_field' value='" + block.text() + "' type='text'/><a href='" + $(this).closest('a').attr("href") + "'><button class='submit_edit' type='submit'>Update</button></a>");
$(".submit_edit").click(function(event)
{
event.preventDefault();
var block_parent = $(this).closest('.editable_parent');
var block = block_parent.find('.editable_block').eq(0);
var newContent = block.find('input').eq(0).val();
block.html(newContent);
var form_action = $(this).closest('a').attr("href");
console.log("Form Action: " + form_action);
$.ajax({
url: form_action + "?session=" + session,
type: "POST",
dataType: "json",
data: {is_js: "1",edit_item: newContent}
});
});
});
}); });

237
routes.go
View File

@ -1,5 +1,6 @@
package main package main
import "errors"
import "log" import "log"
import "fmt" import "fmt"
import "strconv" import "strconv"
@ -58,14 +59,7 @@ func route_custom_page(w http.ResponseWriter, r *http.Request){
pi := Page{"Page","page",user,tList,val} pi := Page{"Page","page",user,tList,val}
templates.ExecuteTemplate(w,"custom_page.html", pi) templates.ExecuteTemplate(w,"custom_page.html", pi)
} else { } else {
errmsg := "The requested page doesn't exist." NotFound(w,r,user)
pi := Page{"Error","error",user,tList,errmsg}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
errpage := b.String()
w.WriteHeader(404)
fmt.Fprintln(w,errpage)
} }
} }
@ -133,7 +127,6 @@ func route_forum(w http.ResponseWriter, r *http.Request){
var( var(
topicList map[int]interface{} topicList map[int]interface{}
currentID int currentID int
fname string
tid int tid int
title string title string
@ -156,18 +149,9 @@ func route_forum(w http.ResponseWriter, r *http.Request){
return return
} }
err = db.QueryRow("select name from forums where fid = ?", fid).Scan(&fname) _, ok := forums[fid]
if err == sql.ErrNoRows { if !ok {
pi := Page{"Error","error",user,tList,"The requested forum doesn't exist."} NotFound(w,r,user)
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
errpage := b.String()
w.WriteHeader(404)
fmt.Fprintln(w,errpage)
return
} else if err != nil {
InternalError(err,w,r,user)
return return
} }
@ -202,7 +186,7 @@ func route_forum(w http.ResponseWriter, r *http.Request){
InternalError(err,w,r,user) InternalError(err,w,r,user)
return return
} }
pi := Page{fname,"forum",user,topicList,0} pi := Page{forums[fid].Name,"forum",user,topicList,0}
err = templates.ExecuteTemplate(w,"forum.html", pi) err = templates.ExecuteTemplate(w,"forum.html", pi)
if err != nil { if err != nil {
InternalError(err, w, r, user) InternalError(err, w, r, user)
@ -211,46 +195,23 @@ func route_forum(w http.ResponseWriter, r *http.Request){
func route_forums(w http.ResponseWriter, r *http.Request){ func route_forums(w http.ResponseWriter, r *http.Request){
user := SessionCheck(w,r) user := SessionCheck(w,r)
var forumList map[int]interface{} var forumList map[int]interface{} = make(map[int]interface{})
forumList = make(map[int]interface{})
currentID := 0 currentID := 0
rows, err := db.Query("select fid, name, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime from forums") for _, forum := range forums {
if err != nil { if forum.Active {
InternalError(err,w,r,user) forumList[currentID] = forum
currentID++
}
}
if len(forums) == 0 {
InternalError(errors.New("No forums"),w,r,user)
return return
} }
defer rows.Close()
for rows.Next() {
forum := Forum{0,"","",0,"",0,""}
err := rows.Scan(&forum.ID, &forum.Name, &forum.LastTopic,&forum.LastTopicID,&forum.LastReplyer,&forum.LastReplyerID,&forum.LastTopicTime)
if err != nil {
InternalError(err,w,r,user)
return
}
if forum.LastTopicID != 0 {
forum.LastTopicTime, err = relative_time(forum.LastTopicTime)
if err != nil {
InternalError(err,w,r,user)
return
}
} else {
forum.LastTopic = "None"
forum.LastTopicTime = ""
}
forumList[currentID] = forum
currentID++
}
err = rows.Err()
if err != nil {
InternalError(err,w,r,user)
return
}
pi := Page{"Forum List","forums",user,forumList,0} pi := Page{"Forum List","forums",user,forumList,0}
err = templates.ExecuteTemplate(w,"forums.html", pi) err := templates.ExecuteTemplate(w,"forums.html", pi)
if err != nil { if err != nil {
InternalError(err, w, r, user) InternalError(err, w, r, user)
} }
@ -290,14 +251,7 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
//err = db.QueryRow("select title, content, createdBy, status, is_closed from topics where tid = ?", tid).Scan(&title, &content, &createdBy, &status, &is_closed) //err = db.QueryRow("select title, content, createdBy, status, is_closed from topics where tid = ?", tid).Scan(&title, &content, &createdBy, &status, &is_closed)
err = db.QueryRow("select topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, users.name, users.avatar, users.is_super_admin, users.group from topics left join users ON topics.createdBy = users.uid where tid = ?", topic.ID).Scan(&topic.Title, &content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.CreatedByName, &topic.Avatar, &is_super_admin, &group) err = db.QueryRow("select topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, users.name, users.avatar, users.is_super_admin, users.group from topics left join users ON topics.createdBy = users.uid where tid = ?", topic.ID).Scan(&topic.Title, &content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.CreatedByName, &topic.Avatar, &is_super_admin, &group)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
errmsg := "The requested topic doesn't exist." NotFound(w,r,user)
pi := Page{"Error","error",user,tList,errmsg}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
errpage := b.String()
w.WriteHeader(404)
fmt.Fprintln(w,errpage)
return return
} else if err != nil { } else if err != nil {
InternalError(err,w,r,user) InternalError(err,w,r,user)
@ -1036,9 +990,9 @@ func route_login_submit(w http.ResponseWriter, r *http.Request) {
var salt string var salt string
var session string var session string
username := html.EscapeString(r.PostFormValue("username")) username := html.EscapeString(r.PostFormValue("username"))
log.Print("Username: " + username) //log.Print("Username: " + username)
password := r.PostFormValue("password") password := r.PostFormValue("password")
log.Print("Password: " + password) //log.Print("Password: " + password)
err = login_stmt.QueryRow(username).Scan(&uid, &username, &real_password, &salt) err = login_stmt.QueryRow(username).Scan(&uid, &username, &real_password, &salt)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
@ -1112,8 +1066,8 @@ func route_login_submit(w http.ResponseWriter, r *http.Request) {
return return
} }
log.Print("Successful Login") //log.Print("Successful Login")
log.Print("Session: " + session) //log.Print("Session: " + session)
cookie := http.Cookie{Name: "uid",Value: strconv.Itoa(uid),Path: "/",MaxAge: year} cookie := http.Cookie{Name: "uid",Value: strconv.Itoa(uid),Path: "/",MaxAge: year}
http.SetCookie(w,&cookie) http.SetCookie(w,&cookie)
cookie = http.Cookie{Name: "session",Value: session,Path: "/",MaxAge: year} cookie = http.Cookie{Name: "session",Value: session,Path: "/",MaxAge: year}
@ -1230,28 +1184,11 @@ func route_panel_forums(w http.ResponseWriter, r *http.Request){
var forumList map[int]interface{} = make(map[int]interface{}) var forumList map[int]interface{} = make(map[int]interface{})
currentID := 0 currentID := 0
rows, err := db.Query("select fid, name from forums") for _, forum := range forums {
if err != nil { if forum.ID > -1 {
InternalError(err,w,r,user) forumList[currentID] = forum
return currentID++
}
defer rows.Close()
for rows.Next() {
forum := ForumSimple{0,""}
err := rows.Scan(&forum.ID, &forum.Name)
if err != nil {
InternalError(err,w,r,user)
return
} }
forumList[currentID] = forum
currentID++
}
err = rows.Err()
if err != nil {
InternalError(err,w,r,user)
return
} }
pi := Page{"Forum Manager","panel-forums",user,forumList,0} pi := Page{"Forum Manager","panel-forums",user,forumList,0}
@ -1264,17 +1201,135 @@ func route_panel_forums_create_submit(w http.ResponseWriter, r *http.Request){
NoPermissions(w,r,user) NoPermissions(w,r,user)
return return
} }
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
LocalError("Bad Form", w, r, user) LocalError("Bad Form", w, r, user)
return return
} }
if r.FormValue("session") != user.Session {
SecurityError(w,r,user)
return
}
_, err = create_forum_stmt.Exec(r.PostFormValue("forum-name")) fname := r.PostFormValue("forum-name")
res, err := create_forum_stmt.Exec(fname)
if err != nil { if err != nil {
InternalError(err,w,r,user) InternalError(err,w,r,user)
return return
} }
http.Redirect(w,r, "/panel/forums/", http.StatusSeeOther) lastId, err := res.LastInsertId()
if err != nil {
InternalError(err,w,r,user)
return
}
forums[int(lastId)] = Forum{int(lastId),fname,true,"",0,"",0,""}
http.Redirect(w,r,"/panel/forums/",http.StatusSeeOther)
}
func route_panel_forums_delete(w http.ResponseWriter, r *http.Request){
user := SessionCheck(w,r)
if !user.Is_Admin {
NoPermissions(w,r,user)
return
}
if r.FormValue("session") != user.Session {
SecurityError(w,r,user)
return
}
fid, err := strconv.Atoi(r.URL.Path[len("/panel/forums/delete/"):])
if err != nil {
LocalError("The provided Forum ID is not a valid number.",w,r,user)
return
}
_, ok := forums[fid];
if !ok {
LocalError("The forum you're trying to delete doesn't exist.",w,r,user)
return
}
confirm_msg := "Are you sure you want to delete the '" + forums[fid].Name + "' forum?"
yousure := AreYouSure{"/panel/forums/delete/submit/" + strconv.Itoa(fid),confirm_msg}
pi := Page{"Delete Forum","panel-forums-delete",user,tList,yousure}
templates.ExecuteTemplate(w,"areyousure.html", pi)
}
func route_panel_forums_delete_submit(w http.ResponseWriter, r *http.Request) {
user := SessionCheck(w,r)
if !user.Is_Admin {
NoPermissions(w,r,user)
return
}
if r.FormValue("session") != user.Session {
SecurityError(w,r,user)
return
}
fid, err := strconv.Atoi(r.URL.Path[len("/panel/forums/delete/submit/"):])
if err != nil {
LocalError("The provided Forum ID is not a valid number.",w,r,user)
return
}
_, ok := forums[fid];
if !ok {
LocalError("The forum you're trying to delete doesn't exist.",w,r,user)
return
}
_, err = delete_forum_stmt.Exec(fid)
if err != nil {
InternalError(err,w,r,user)
return
}
// Remove this forum from the forum cache
delete(forums,fid);
http.Redirect(w,r,"/panel/forums/",http.StatusSeeOther)
}
func route_panel_forums_edit_submit(w http.ResponseWriter, r *http.Request) {
user := SessionCheck(w,r)
if !user.Is_Admin {
NoPermissions(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
}
fid, err := strconv.Atoi(r.URL.Path[len("/panel/forums/edit/submit/"):])
if err != nil {
LocalError("The provided Forum ID is not a valid number.",w,r,user)
return
}
forum_name := r.PostFormValue("edit_item")
forum, ok := forums[fid];
if !ok {
LocalError("The forum you're trying to edit doesn't exist.",w,r,user)
return
}
_, err = update_forum_stmt.Exec(forum_name, fid)
if err != nil {
InternalError(err,w,r,user)
return
}
forum.Name = forum_name
forums[fid] = forum
http.Redirect(w,r,"/panel/forums/",http.StatusSeeOther)
} }

10
templates/areyousure.html Normal file
View File

@ -0,0 +1,10 @@
{{template "header.html" . }}
<div class="rowblock">
<div class="rowitem"><a>Are you sure?</a></div>
</div>
<div class="rowblock">
<div class="rowitem passive">{{.Something.Message}}<br /><br />
<a class="username" href="{{.Something.URL}}?session={{.CurrentUser.Session}}">Continue</a>
</div>
</div>
{{template "footer.html" . }}

View File

@ -4,6 +4,9 @@
<title>{{.Title}}</title> <title>{{.Title}}</title>
<link href="/static/main.css" rel="stylesheet" type="text/css"> <link href="/static/main.css" rel="stylesheet" type="text/css">
<script type="text/javascript" src="/static/jquery-1.12.3.min.js"></script> <script type="text/javascript" src="/static/jquery-1.12.3.min.js"></script>
<script type="text/javascript">
var session = "{{.CurrentUser.Session}}";
</script>
<script type="text/javascript" src="/static/global.js"></script> <script type="text/javascript" src="/static/global.js"></script>
</head> </head>
<body> <body>

View File

@ -5,18 +5,24 @@
<div class="rowitem passive"><a>Coming Soon</a></div> <div class="rowitem passive"><a>Coming Soon</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div> <div class="rowitem passive"><a>Coming Soon</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div> <div class="rowitem passive"><a>Coming Soon</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div> <div class="rowitem passive"><a href="/forum/-1">Reports</a></div>
</div> </div>
<div class="colblock_right"> <div class="colblock_right">
{{range .ItemList}} {{range .ItemList}}
<div class="rowitem"><a style="font-size: 20px;position:relative;top: -2px;font-weight: normal;text-transform: none;">{{.Name}}</a></div> <div class="rowitem editable_parent" style="font-weight: normal;">
<a class="editable_block" style="font-size: 20px;position:relative;top: -2px;text-transform: none;">{{.Name}}</a>
<span style="float: right;">
<a href="/panel/forums/edit/submit/{{.ID}}" class="username edit_field">Edit</a>
<a href="/panel/forums/delete/{{.ID}}?session={{$.CurrentUser.Session}}" class="username">Delete</a>
</span>
</div>
{{end}} {{end}}
</div><br /> </div><br />
<div class="colblock_right"> <div class="colblock_right">
<div class="rowitem"><a>Add Forum</a></div> <div class="rowitem"><a>Add Forum</a></div>
</div> </div>
<div class="colblock_right"> <div class="colblock_right">
<form action="/panel/forums/create/" method="post"> <form action="/panel/forums/create/?session={{.CurrentUser.Session}}" method="post">
<div class="formrow"> <div class="formrow">
<div class="formitem"><a>Forum Name</a></div> <div class="formitem"><a>Forum Name</a></div>
<div class="formitem"><input name="forum-name" type="text" /></div> <div class="formitem"><input name="forum-name" type="text" /></div>