Admins now show up in purple on posts.

Added a .gitignore file.
Added group caching.
Improved the forum list.
Added the Member group.
Opening posts are now parsed properly.
Fixed a bug where the avatar of the opening poster was applied to everyone.
Only admins can see moderation options now.
This commit is contained in:
Azareal 2016-12-04 06:16:59 +00:00
parent 771af02674
commit 1f6e585296
19 changed files with 81 additions and 20 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
src/uploads/*
bin/*

View File

@ -33,7 +33,7 @@ Set the password column of your user account in the database to what you want yo
# Run the program # Run the program
go run errors.go main.go pages.go reply.go routes.go topic.go user.go utils.go forum.go config.go go run errors.go main.go pages.go reply.go routes.go topic.go user.go utils.go forum.go group.go config.go
Alternatively, you could run the run.bat batch file on Windows. Alternatively, you could run the run.bat batch file on Windows.
@ -65,4 +65,4 @@ Tweak the CSS to make it responsive.
Add a forum cache. Add a forum cache.
Add a group cache. Cache the static files in memory.

BIN
forum-list.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 164 KiB

View File

Before

Width:  |  Height:  |  Size: 312 KiB

After

Width:  |  Height:  |  Size: 312 KiB

BIN
ren6.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -11,4 +11,5 @@ var dbport = "3306" // You probably won't need to change this
var max_request_size = 5 * megabyte 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;"

View File

@ -64,6 +64,7 @@ CREATE TABLE `replies`(
INSERT INTO users(`name`,`group`,`is_super_admin`,`createdAt`,`lastActiveAt`) INSERT INTO users(`name`,`group`,`is_super_admin`,`createdAt`,`lastActiveAt`)
VALUES ('Admin',1,1,NOW(),NOW()); VALUES ('Admin',1,1,NOW(),NOW());
INSERT INTO users_groups(`name`,`permissions`,`is_admin`) VALUES ('Administrator','{}',1); INSERT INTO users_groups(`name`,`permissions`,`is_admin`) VALUES ('Administrator','{}',1);
INSERT INTO users_groups(`name`,`permissions`) VALUES ('Member','{}');
INSERT INTO forums(`name`,`lastTopicTime`) VALUES ('General',NOW()); INSERT INTO forums(`name`,`lastTopicTime`) VALUES ('General',NOW());
INSERT INTO topics(`title`,`content`,`createdAt`,`lastReplyAt`,`createdBy`,`parentID`) INSERT INTO topics(`title`,`content`,`createdAt`,`lastReplyAt`,`createdBy`,`parentID`)
VALUES ('Test Topic','A topic automatically generated by the software.',NOW(),NOW(),1,1); VALUES ('Test Topic','A topic automatically generated by the software.',NOW(),NOW(),1,1);

10
src/group.go Normal file
View File

@ -0,0 +1,10 @@
package main
type Group struct
{
ID int
Name string
Permissions string
Is_Admin bool
Is_Banned bool
}

View File

@ -17,6 +17,7 @@ const kilobyte int = 1024
const megabyte int = 1024 * 1024 const megabyte int = 1024 * 1024
const saltLength int = 32 const saltLength int = 32
const sessionLength int = 80 const sessionLength int = 80
var db *sql.DB var db *sql.DB
var get_session_stmt *sql.Stmt var get_session_stmt *sql.Stmt
var create_topic_stmt *sql.Stmt var create_topic_stmt *sql.Stmt
@ -37,8 +38,12 @@ var set_avatar_stmt *sql.Stmt
var set_username_stmt *sql.Stmt var set_username_stmt *sql.Stmt
var register_stmt *sql.Stmt var register_stmt *sql.Stmt
var username_exists_stmt *sql.Stmt var username_exists_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 staff_css_tmpl = template.CSS(staff_css)
var groups map[int]Group = make(map[int]Group)
func init_database(err error) { func init_database(err error) {
if(dbpassword != ""){ if(dbpassword != ""){
@ -161,7 +166,7 @@ func init_database(err error) {
// create_account_stmt, err = db.Prepare("INSERT INTO // create_account_stmt, err = db.Prepare("INSERT INTO
log.Print("Preparing register statement.") log.Print("Preparing register statement.")
register_stmt, err = db.Prepare("INSERT INTO users(`name`,`password`,`salt`,`group`,`is_super_admin`,`session`) VALUES(?,?,?,0,0,?)") register_stmt, err = db.Prepare("INSERT INTO users(`name`,`password`,`salt`,`group`,`is_super_admin`,`session`) VALUES(?,?,?,2,0,?)")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -171,6 +176,26 @@ func init_database(err error) {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
log.Print("Loading the usergroups.")
rows, err := db.Query("select gid,name,permissions,is_admin,is_banned from users_groups")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
group := Group{0,"","",false,false}
err := rows.Scan(&group.ID, &group.Name, &group.Permissions, &group.Is_Admin, &group.Is_Banned)
if err != nil {
log.Fatal(err)
}
groups[group.ID] = group
}
err = rows.Err()
if err != nil {
log.Fatal(err)
}
} }
func main(){ func main(){

View File

@ -251,11 +251,17 @@ button
padding-top: 2px; padding-top: 2px;
padding-bottom: 2px; padding-bottom: 2px;
color: #505050; /* 80,80,80 */ color: #505050; /* 80,80,80 */
background-color: #FFFFFF;
border-style: dotted; border-style: dotted;
border-color: #505050; /* 232,232,232. All three RGB colours being the same seems to create a shade of gray */ border-color: #505050; /* 232,232,232. All three RGB colours being the same seems to create a shade of gray */
border-width: 1px; border-width: 1px;
font-size: 15px; font-size: 15px;
} }
button.username
{
position: relative;
top: -0.25px;
}
.show_on_edit .show_on_edit
{ {

BIN
src/public/white-dot.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

View File

@ -13,4 +13,5 @@ type Reply struct
LastEdit int LastEdit int
LastEditBy int LastEditBy int
Avatar string Avatar string
Css template.CSS
} }

View File

@ -92,7 +92,7 @@ func route_topics(w http.ResponseWriter, r *http.Request){
avatar = "/uploads/avatar_" + strconv.Itoa(createdBy) + avatar avatar = "/uploads/avatar_" + strconv.Itoa(createdBy) + avatar
} }
topicList[currentID] = TopicUser{tid, title, content, createdBy, is_closed, sticky, createdAt,parentID, status, name, avatar} topicList[currentID] = TopicUser{tid, title, content, createdBy, is_closed, sticky, createdAt,parentID, status, name, avatar, ""}
currentID++ currentID++
} }
err = rows.Err() err = rows.Err()
@ -173,7 +173,7 @@ func route_forum(w http.ResponseWriter, r *http.Request){
avatar = "/uploads/avatar_" + strconv.Itoa(createdBy) + avatar avatar = "/uploads/avatar_" + strconv.Itoa(createdBy) + avatar
} }
topicList[currentID] = TopicUser{tid, title, content, createdBy, is_closed, sticky, createdAt,parentID, status, name, avatar} topicList[currentID] = TopicUser{tid, title, content, createdBy, is_closed, sticky, createdAt,parentID, status, name, avatar, ""}
currentID++ currentID++
} }
err = rows.Err() err = rows.Err()
@ -243,13 +243,16 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
replyLastEdit int replyLastEdit int
replyLastEditBy int replyLastEditBy int
replyAvatar string replyAvatar string
replyCss template.CSS
is_super_admin bool
group int
currentID int currentID int
replyList map[int]interface{} replyList map[int]interface{}
) )
replyList = make(map[int]interface{}) replyList = make(map[int]interface{})
currentID = 0 currentID = 0
topic := TopicUser{0,"","",0,false,false,"",0,"","",""} topic := TopicUser{0,"","",0,false,false,"",0,"","","",no_css_tmpl}
topic.ID, err = strconv.Atoi(r.URL.Path[len("/topic/"):]) topic.ID, err = strconv.Atoi(r.URL.Path[len("/topic/"):])
if err != nil { if err != nil {
@ -259,7 +262,7 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
// Get the topic.. // Get the topic..
//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 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) 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." errmsg := "The requested topic doesn't exist."
pi := Page{"Error","error",user,tList,errmsg} pi := Page{"Error","error",user,tList,errmsg}
@ -275,7 +278,7 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
return return
} }
topic.Content = template.HTML(content) topic.Content = template.HTML(parse_message(content))
if topic.Is_Closed { if topic.Is_Closed {
topic.Status = "closed" topic.Status = "closed"
} else { } else {
@ -284,10 +287,13 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
if topic.Avatar != "" && topic.Avatar[0] == '.' { if topic.Avatar != "" && topic.Avatar[0] == '.' {
topic.Avatar = "/uploads/avatar_" + strconv.Itoa(topic.CreatedBy) + topic.Avatar topic.Avatar = "/uploads/avatar_" + strconv.Itoa(topic.CreatedBy) + topic.Avatar
} }
if is_super_admin || groups[group].Is_Admin {
topic.Css = staff_css_tmpl
}
// Get the replies.. // Get the replies..
//rows, err := db.Query("select rid, content, createdBy, createdAt from replies where tid = ?", tid) //rows, err := db.Query("select rid, content, createdBy, createdAt from replies where tid = ?", tid)
rows, err := db.Query("select replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name from replies left join users ON replies.createdBy = users.uid where tid = ?", topic.ID) rows, err := db.Query("select replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.is_super_admin, users.group from replies left join users ON replies.createdBy = users.uid where tid = ?", topic.ID)
if err != nil { if err != nil {
InternalError(err,w,r,user) InternalError(err,w,r,user)
return return
@ -295,17 +301,22 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
err := rows.Scan(&rid, &replyContent, &replyCreatedBy, &replyCreatedAt, &replyLastEdit, &replyLastEditBy, &replyAvatar, &replyCreatedByName) err := rows.Scan(&rid, &replyContent, &replyCreatedBy, &replyCreatedAt, &replyLastEdit, &replyLastEditBy, &replyAvatar, &replyCreatedByName, &is_super_admin, &group)
if err != nil { if err != nil {
InternalError(err,w,r,user) InternalError(err,w,r,user)
return return
} }
if is_super_admin || groups[group].Is_Admin {
replyCss = staff_css_tmpl
} else {
replyCss = no_css_tmpl
}
if replyAvatar != "" && replyAvatar[0] == '.' { if replyAvatar != "" && replyAvatar[0] == '.' {
replyAvatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + replyAvatar replyAvatar = "/uploads/avatar_" + strconv.Itoa(replyCreatedBy) + replyAvatar
} }
replyList[currentID] = Reply{rid,topic.ID,replyContent,template.HTML(parse_message(replyContent)),replyCreatedBy,replyCreatedByName,replyCreatedAt,replyLastEdit,replyLastEditBy,replyAvatar} replyList[currentID] = Reply{rid,topic.ID,replyContent,template.HTML(parse_message(replyContent)),replyCreatedBy,replyCreatedByName,replyCreatedAt,replyLastEdit,replyLastEditBy,replyAvatar,replyCss}
currentID++ currentID++
} }
err = rows.Err() err = rows.Err()

View File

@ -1,2 +1,2 @@
go run errors.go main.go pages.go reply.go routes.go topic.go user.go utils.go config.go forum.go go run errors.go main.go pages.go reply.go routes.go topic.go user.go utils.go config.go forum.go group.go
pause pause

View File

@ -1,7 +1,7 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="rowblock"> <div class="rowblock">
{{range .ItemList}}<div class="rowitem"> {{range .ItemList}}<div class="rowitem">
<a href="/forum/{{.ID}}">{{.Name}}</a> <a href="/forum/{{.ID}}" style="font-size: 20px;position:relative;top: -2px;font-weight: normal;text-transform: none;">{{.Name}}</a>
<a href="/topic/{{.LastTopicID}}" style="font-weight: normal;text-transform: none;float: right;">{{.LastTopic}} <small style="font-size: 12px;">{{.LastTopicTime}}</small></a> <a href="/topic/{{.LastTopicID}}" style="font-weight: normal;text-transform: none;float: right;">{{.LastTopic}} <small style="font-size: 12px;">{{.LastTopicTime}}</small></a>
</div>{{end}} </div>{{end}}
</div> </div>

View File

@ -4,6 +4,7 @@
<div class="rowitem"{{ if .Something.Sticky }}style="background-color: #FFFFEA;"{{end}}> <div class="rowitem"{{ if .Something.Sticky }}style="background-color: #FFFFEA;"{{end}}>
<a class='topic_name hide_on_edit'>{{.Something.Title}}</a> <a class='topic_name hide_on_edit'>{{.Something.Title}}</a>
<span class='topic_status topic_status_e topic_status_{{.Something.Status}} hide_on_edit'>{{.Something.Status}}</span> <span class='topic_status topic_status_e topic_status_{{.Something.Status}} hide_on_edit'>{{.Something.Status}}</span>
{{if .CurrentUser.Is_Admin}}
<a href='/topic/edit/{{.Something.ID}}' class="username hide_on_edit open_edit" style="font-weight: normal;">Edit</a> <a href='/topic/edit/{{.Something.ID}}' class="username hide_on_edit open_edit" style="font-weight: normal;">Edit</a>
<a href='/topic/delete/submit/{{.Something.ID}}' class="username" style="font-weight: normal;">Delete</a> <a href='/topic/delete/submit/{{.Something.ID}}' class="username" style="font-weight: normal;">Delete</a>
{{ if .Something.Sticky }}<a href='/topic/unstick/submit/{{.Something.ID}}' class="username" style="font-weight: normal;">Unpin</a>{{else}}<a href='/topic/stick/submit/{{.Something.ID}}' class="username" style="font-weight: normal;">Pin</a>{{end}} {{ if .Something.Sticky }}<a href='/topic/unstick/submit/{{.Something.ID}}' class="username" style="font-weight: normal;">Unpin</a>{{else}}<a href='/topic/stick/submit/{{.Something.ID}}' class="username" style="font-weight: normal;">Pin</a>{{end}}
@ -14,11 +15,12 @@
<option>closed</option> <option>closed</option>
</select> </select>
<button name="topic-button" class="formbutton show_on_edit submit_edit">Update</button> <button name="topic-button" class="formbutton show_on_edit submit_edit">Update</button>
{{end}}
</div> </div>
</form> </form>
</div> </div>
<div class="rowblock"> <div class="rowblock">
<div class="rowitem passive editable_parent" style="border-bottom: none;{{ if .Something.Avatar }}background-image: url({{ .Something.Avatar }});background-position: left;background-repeat: no-repeat;background-size: 128px;padding-left: 136px;{{end}}"> <div class="rowitem passive editable_parent" style="border-bottom: none;{{ if .Something.Avatar }}background-image: url({{ .Something.Avatar }}), url(/static/white-dot.jpg);background-position: top left;background-repeat: no-repeat, repeat-y;background-size: 128px;padding-left: 136px;{{.Something.Css}}{{end}}">
<span class="hide_on_edit topic_content">{{.Something.Content}}</span> <span class="hide_on_edit topic_content">{{.Something.Content}}</span>
<textarea name="topic_content" class="show_on_edit topic_content_input">{{.Something.Content}}</textarea> <textarea name="topic_content" class="show_on_edit topic_content_input">{{.Something.Content}}</textarea>
<br /><br /> <br /><br />
@ -27,12 +29,12 @@
</div><br /> </div><br />
<div class="rowblock" style="overflow: hidden;"> <div class="rowblock" style="overflow: hidden;">
{{range $index, $element := .ItemList}} {{range $index, $element := .ItemList}}
<div class="rowitem passive deletable_block editable_parent" style="{{ if $element.Avatar }}background-image: url({{ $element.Avatar }});background-position: left;background-repeat: no-repeat;background-size: 128px;padding-left: 136px;{{end}}"> <div class="rowitem passive deletable_block editable_parent" style="{{ if $element.Avatar }}background-image: url({{$element.Avatar}}), url(/static/white-dot.jpg);background-position: top left;background-repeat: no-repeat, repeat-y;background-size: 128px;padding-left: 136px;{{$element.Css}}{{end}}">
<span class="editable_block">{{$element.ContentHtml}}</span> <span class="editable_block">{{$element.ContentHtml}}</span>
<br /><br /> <br /><br />
<a class="username">{{$element.CreatedByName}}<a/> <a class="username">{{$element.CreatedByName}}</a>
<a href="/reply/edit/submit/{{$element.ID}}"><button class="username edit_item">Edit</button></a> {{if $.CurrentUser.Is_Admin}}<a href="/reply/edit/submit/{{$element.ID}}"><button class="username edit_item">Edit</button></a>
<a href="/reply/delete/submit/{{$element.ID}}"><button class="username delete_item">Delete</button></a> <a href="/reply/delete/submit/{{$element.ID}}"><button class="username delete_item">Delete</button></a>{{end}}
</div>{{ end }} </div>{{ end }}
</div> </div>
<div class="rowblock"> <div class="rowblock">

View File

@ -1,4 +1,5 @@
package main package main
import "html/template"
type Topic struct type Topic struct
{ {
@ -27,4 +28,5 @@ type TopicUser struct
CreatedByName string CreatedByName string
Avatar string Avatar string
Css template.CSS
} }

BIN
staff-posts.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB