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.
|
@ -0,0 +1,2 @@
|
|||
src/uploads/*
|
||||
bin/*
|
|
@ -33,7 +33,7 @@ Set the password column of your user account in the database to what you want yo
|
|||
|
||||
# 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.
|
||||
|
||||
|
@ -65,4 +65,4 @@ Tweak the CSS to make it responsive.
|
|||
|
||||
Add a forum cache.
|
||||
|
||||
Add a group cache.
|
||||
Cache the static files in memory.
|
||||
|
|
After Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 164 KiB |
Before Width: | Height: | Size: 312 KiB After Width: | Height: | Size: 312 KiB |
|
@ -12,3 +12,4 @@ var max_request_size = 5 * megabyte
|
|||
|
||||
// Misc
|
||||
var default_route = route_topics
|
||||
var staff_css = "background-color: #ffeaff;background-position: left;"
|
|
@ -64,6 +64,7 @@ CREATE TABLE `replies`(
|
|||
INSERT INTO users(`name`,`group`,`is_super_admin`,`createdAt`,`lastActiveAt`)
|
||||
VALUES ('Admin',1,1,NOW(),NOW());
|
||||
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 topics(`title`,`content`,`createdAt`,`lastReplyAt`,`createdBy`,`parentID`)
|
||||
VALUES ('Test Topic','A topic automatically generated by the software.',NOW(),NOW(),1,1);
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
type Group struct
|
||||
{
|
||||
ID int
|
||||
Name string
|
||||
Permissions string
|
||||
Is_Admin bool
|
||||
Is_Banned bool
|
||||
}
|
27
src/main.go
|
@ -17,6 +17,7 @@ const kilobyte int = 1024
|
|||
const megabyte int = 1024 * 1024
|
||||
const saltLength int = 32
|
||||
const sessionLength int = 80
|
||||
|
||||
var db *sql.DB
|
||||
var get_session_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 register_stmt *sql.Stmt
|
||||
var username_exists_stmt *sql.Stmt
|
||||
|
||||
var custom_pages map[string]string = make(map[string]string)
|
||||
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) {
|
||||
if(dbpassword != ""){
|
||||
|
@ -161,7 +166,7 @@ func init_database(err error) {
|
|||
// create_account_stmt, err = db.Prepare("INSERT INTO
|
||||
|
||||
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 {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
@ -171,6 +176,26 @@ func init_database(err error) {
|
|||
if err != nil {
|
||||
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(){
|
||||
|
|
|
@ -251,11 +251,17 @@ button
|
|||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
color: #505050; /* 80,80,80 */
|
||||
background-color: #FFFFFF;
|
||||
border-style: dotted;
|
||||
border-color: #505050; /* 232,232,232. All three RGB colours being the same seems to create a shade of gray */
|
||||
border-width: 1px;
|
||||
font-size: 15px;
|
||||
}
|
||||
button.username
|
||||
{
|
||||
position: relative;
|
||||
top: -0.25px;
|
||||
}
|
||||
|
||||
.show_on_edit
|
||||
{
|
||||
|
|
After Width: | Height: | Size: 539 B |
|
@ -13,4 +13,5 @@ type Reply struct
|
|||
LastEdit int
|
||||
LastEditBy int
|
||||
Avatar string
|
||||
Css template.CSS
|
||||
}
|
||||
|
|
|
@ -92,7 +92,7 @@ func route_topics(w http.ResponseWriter, r *http.Request){
|
|||
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++
|
||||
}
|
||||
err = rows.Err()
|
||||
|
@ -173,7 +173,7 @@ func route_forum(w http.ResponseWriter, r *http.Request){
|
|||
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++
|
||||
}
|
||||
err = rows.Err()
|
||||
|
@ -243,13 +243,16 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
|
|||
replyLastEdit int
|
||||
replyLastEditBy int
|
||||
replyAvatar string
|
||||
replyCss template.CSS
|
||||
is_super_admin bool
|
||||
group int
|
||||
|
||||
currentID int
|
||||
replyList map[int]interface{}
|
||||
)
|
||||
replyList = make(map[int]interface{})
|
||||
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/"):])
|
||||
if err != nil {
|
||||
|
@ -259,7 +262,7 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
|
|||
|
||||
// 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 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 {
|
||||
errmsg := "The requested topic doesn't exist."
|
||||
pi := Page{"Error","error",user,tList,errmsg}
|
||||
|
@ -275,7 +278,7 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
|
|||
return
|
||||
}
|
||||
|
||||
topic.Content = template.HTML(content)
|
||||
topic.Content = template.HTML(parse_message(content))
|
||||
if topic.Is_Closed {
|
||||
topic.Status = "closed"
|
||||
} else {
|
||||
|
@ -284,10 +287,13 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
|
|||
if topic.Avatar != "" && topic.Avatar[0] == '.' {
|
||||
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..
|
||||
//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 {
|
||||
InternalError(err,w,r,user)
|
||||
return
|
||||
|
@ -295,17 +301,22 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
|
|||
defer rows.Close()
|
||||
|
||||
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 {
|
||||
InternalError(err,w,r,user)
|
||||
return
|
||||
}
|
||||
|
||||
if is_super_admin || groups[group].Is_Admin {
|
||||
replyCss = staff_css_tmpl
|
||||
} else {
|
||||
replyCss = no_css_tmpl
|
||||
}
|
||||
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++
|
||||
}
|
||||
err = rows.Err()
|
||||
|
|
|
@ -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
|
|
@ -1,7 +1,7 @@
|
|||
{{template "header.html" . }}
|
||||
<div class="rowblock">
|
||||
{{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>
|
||||
</div>{{end}}
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<div class="rowitem"{{ if .Something.Sticky }}style="background-color: #FFFFEA;"{{end}}>
|
||||
<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>
|
||||
{{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/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}}
|
||||
|
@ -14,11 +15,12 @@
|
|||
<option>closed</option>
|
||||
</select>
|
||||
<button name="topic-button" class="formbutton show_on_edit submit_edit">Update</button>
|
||||
{{end}}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<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>
|
||||
<textarea name="topic_content" class="show_on_edit topic_content_input">{{.Something.Content}}</textarea>
|
||||
<br /><br />
|
||||
|
@ -27,12 +29,12 @@
|
|||
</div><br />
|
||||
<div class="rowblock" style="overflow: hidden;">
|
||||
{{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>
|
||||
<br /><br />
|
||||
<a class="username">{{$element.CreatedByName}}<a/>
|
||||
<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 class="username">{{$element.CreatedByName}}</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>{{end}}
|
||||
</div>{{ end }}
|
||||
</div>
|
||||
<div class="rowblock">
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
package main
|
||||
import "html/template"
|
||||
|
||||
type Topic struct
|
||||
{
|
||||
|
@ -27,4 +28,5 @@ type TopicUser struct
|
|||
|
||||
CreatedByName string
|
||||
Avatar string
|
||||
Css template.CSS
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 309 KiB |