Added the ability to assign users as moderators without also giving them the administrative tools.

Profile owners can now moderate their own profiles.
Fixed an issue with database entries being set as null.
A message now appears when there aren't any topics in a forum or in the global topics list.
Added the link to the current user's profile to the main menu.

Database Changes.
not nulled some columns.
Added is_mod column to users_groups.
Added the Moderator usergroup.
This commit is contained in:
Azareal 2016-12-07 13:46:14 +00:00
parent 72d7beefe9
commit 6a320edbb4
13 changed files with 96 additions and 70 deletions

View File

@ -4,14 +4,14 @@ CREATE TABLE `users`(
`uid` int not null AUTO_INCREMENT,
`name` varchar(100) not null,
`password` varchar(100) not null,
`salt` varchar(80) DEFAULT '',
`salt` varchar(80) DEFAULT '' not null,
`group` int not null,
`is_super_admin` tinyint(1) not null,
`createdAt` datetime not null,
`lastActiveAt` datetime not null,
`session` varchar(200) DEFAULT '',
`email` varchar(200) DEFAULT '',
`avatar` varchar(20) DEFAULT '',
`session` varchar(200) DEFAULT '' not null,
`email` varchar(200) DEFAULT '' not null,
`avatar` varchar(20) DEFAULT '' not null,
primary key(`uid`)
);
@ -19,6 +19,7 @@ CREATE TABLE `users_groups`(
`gid` int not null AUTO_INCREMENT,
`name` varchar(100) not null,
`permissions` text not null,
`is_mod` tinyint DEFAULT 0 not null,
`is_admin` tinyint DEFAULT 0 not null,
`is_banned` tinyint DEFAULT 0 not null,
`tag` varchar(50) DEFAULT '' not null,
@ -85,8 +86,9 @@ CREATE TABLE `users_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`,`tag`) VALUES ('Admin','{}',1,"Admin");
INSERT INTO users_groups(`name`,`permissions`,`is_mod`,`is_admin`,`tag`) VALUES ('Administrator','{}',1,1,"Admin");
INSERT INTO users_groups(`name`,`permissions`) VALUES ('Member','{}');
INSERT INTO users_groups(`name`,`permissions`,`is_mod`,`tag`) VALUES ('Moderator','{}',1,"Mod");
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);

Binary file not shown.

Binary file not shown.

View File

@ -5,6 +5,7 @@ type Group struct
ID int
Name string
Permissions string
Is_Mod bool
Is_Admin bool
Is_Banned bool
Tag string

View File

@ -225,15 +225,15 @@ func init_database(err error) {
}
log.Print("Loading the usergroups.")
rows, err := db.Query("SELECT gid,name,permissions,is_admin,is_banned,tag FROM users_groups")
rows, err := db.Query("SELECT gid,name,permissions,is_mod,is_admin,is_banned,tag 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, &group.Tag)
group := Group{0,"","",false,false,false,""}
err := rows.Scan(&group.ID, &group.Name, &group.Permissions, &group.Is_Mod, &group.Is_Admin, &group.Is_Banned, &group.Tag)
if err != nil {
log.Fatal(err)
}

View File

@ -20,7 +20,7 @@ func route_edit_topic(w http.ResponseWriter, r *http.Request) {
is_js = "0"
}
if !user.Is_Admin {
if !user.Is_Mod && !user.Is_Admin {
NoPermissionsJSQ(w,r,user,is_js)
return
}
@ -62,7 +62,7 @@ func route_edit_topic(w http.ResponseWriter, r *http.Request) {
func route_delete_topic(w http.ResponseWriter, r *http.Request) {
user := SessionCheck(w,r)
if !user.Is_Admin {
if !user.Is_Mod && !user.Is_Admin {
NoPermissions(w,r,user)
return
}
@ -94,7 +94,7 @@ func route_delete_topic(w http.ResponseWriter, r *http.Request) {
func route_stick_topic(w http.ResponseWriter, r *http.Request) {
user := SessionCheck(w,r)
if !user.Is_Admin {
if !user.Is_Mod && !user.Is_Admin {
NoPermissions(w,r,user)
return
}
@ -116,7 +116,7 @@ func route_stick_topic(w http.ResponseWriter, r *http.Request) {
func route_unstick_topic(w http.ResponseWriter, r *http.Request) {
user := SessionCheck(w,r)
if !user.Is_Admin {
if !user.Is_Mod && !user.Is_Admin {
NoPermissions(w,r,user)
return
}
@ -149,7 +149,7 @@ func route_reply_edit_submit(w http.ResponseWriter, r *http.Request) {
is_js = "0"
}
if !user.Is_Admin {
if !user.Is_Mod && !user.Is_Admin {
NoPermissionsJSQ(w,r,user,is_js)
return
}
@ -195,7 +195,7 @@ func route_reply_delete_submit(w http.ResponseWriter, r *http.Request) {
is_js = "0"
}
if !user.Is_Admin {
if !user.Is_Mod && !user.Is_Admin {
NoPermissionsJSQ(w,r,user,is_js)
return
}
@ -243,27 +243,27 @@ func route_profile_reply_edit_submit(w http.ResponseWriter, r *http.Request) {
is_js = "0"
}
if !user.Is_Admin {
NoPermissionsJSQ(w,r,user,is_js)
return
}
rid, err := strconv.Atoi(r.URL.Path[len("/profile/reply/edit/submit/"):])
if err != nil {
LocalError("The provided Reply ID is not a valid number.",w,r,user)
return
}
content := html.EscapeString(r.PostFormValue("edit_item"))
_, err = edit_profile_reply_stmt.Exec(content, parse_message(content), rid)
// Get the Reply ID..
var uid int
err = db.QueryRow("select uid from users_replies where rid = ?", rid).Scan(&uid)
if err != nil {
InternalError(err,w,r,user)
return
}
// Get the Reply ID..
var uid int
err = db.QueryRow("select tid from users_replies where rid = ?", rid).Scan(&uid)
if user.ID != uid && !user.Is_Mod && !user.Is_Admin {
NoPermissionsJSQ(w,r,user,is_js)
return
}
content := html.EscapeString(r.PostFormValue("edit_item"))
_, err = edit_profile_reply_stmt.Exec(content, parse_message(content), rid)
if err != nil {
InternalError(err,w,r,user)
return
@ -289,11 +289,6 @@ func route_profile_reply_delete_submit(w http.ResponseWriter, r *http.Request) {
is_js = "0"
}
if !user.Is_Admin {
NoPermissionsJSQ(w,r,user,is_js)
return
}
rid, err := strconv.Atoi(r.URL.Path[len("/profile/reply/delete/submit/"):])
if err != nil {
LocalErrorJSQ("The provided Reply ID is not a valid number.",w,r,user,is_js)
@ -310,6 +305,11 @@ func route_profile_reply_delete_submit(w http.ResponseWriter, r *http.Request) {
return
}
if user.ID != uid && !user.Is_Mod && !user.Is_Admin {
NoPermissionsJSQ(w,r,user,is_js)
return
}
_, err = delete_profile_reply_stmt.Exec(rid)
if err != nil {
InternalErrorJSQ(err,w,r,user,is_js)

View File

@ -119,7 +119,15 @@ func route_topics(w http.ResponseWriter, r *http.Request){
InternalError(err,w,r,user)
return
}
pi := Page{"Topic List","topics",user,topicList,0}
var msg string
if len(topicList) == 0 {
msg = "There aren't any topics yet."
} else {
msg = ""
}
pi := Page{"Topic List","topics",user,topicList,msg}
err = templates.ExecuteTemplate(w,"topics.html", pi)
if err != nil {
InternalError(err, w, r, user)
@ -194,7 +202,15 @@ func route_forum(w http.ResponseWriter, r *http.Request){
InternalError(err,w,r,user)
return
}
pi := Page{forums[fid].Name,"forum",user,topicList,0}
var msg string
if len(topicList) == 0 {
msg = "There aren't any topics in this forum yet."
} else {
msg = ""
}
pi := Page{forums[fid].Name,"forum",user,topicList,msg}
err = templates.ExecuteTemplate(w,"forum.html", pi)
if err != nil {
InternalError(err, w, r, user)
@ -283,7 +299,7 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
} else {
topic.Avatar = strings.Replace(noavatar,"{id}",strconv.Itoa(topic.CreatedBy),1)
}
if is_super_admin || groups[group].Is_Admin {
if is_super_admin || groups[group].Is_Mod || groups[group].Is_Admin {
topic.Css = staff_css_tmpl
}
if groups[group].Tag != "" {
@ -309,7 +325,7 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
}
replyLines = strings.Count(replyContent,"\n")
if is_super_admin || groups[group].Is_Admin {
if is_super_admin || groups[group].Is_Mod || groups[group].Is_Admin {
replyCss = staff_css_tmpl
} else {
replyCss = no_css_tmpl
@ -367,24 +383,32 @@ func route_profile(w http.ResponseWriter, r *http.Request){
replyList = make(map[int]interface{})
currentID = 0
puser := User{0,"",0,false,false,false,"",false,""}
puser := User{0,"",0,false,false,false,false,false,"",false,""}
puser.ID, err = strconv.Atoi(r.URL.Path[len("/user/"):])
if err != nil {
LocalError("The provided TopicID is not a valid number.",w,r,user)
return
}
// Fetch the user data
err = db.QueryRow("SELECT `name`, `group`, `is_super_admin`, `avatar` FROM `users` WHERE `uid` = ?", puser.ID).Scan(&puser.Name, &puser.Group, &puser.Is_Super_Admin, &puser.Avatar)
if err == sql.ErrNoRows {
NotFound(w,r,user)
return
} else if err != nil {
InternalError(err,w,r,user)
return
if puser.ID == user.ID {
user.Is_Mod = true
puser = user
} else {
// Fetch the user data
err = db.QueryRow("SELECT `name`, `group`, `is_super_admin`, `avatar` FROM `users` WHERE `uid` = ?", puser.ID).Scan(&puser.Name, &puser.Group, &puser.Is_Super_Admin, &puser.Avatar)
if err == sql.ErrNoRows {
NotFound(w,r,user)
return
} else if err != nil {
InternalError(err,w,r,user)
return
}
puser.Is_Admin = puser.Is_Super_Admin || groups[puser.Group].Is_Admin
puser.Is_Super_Mod = puser.Is_Admin || groups[puser.Group].Is_Mod
puser.Is_Mod = puser.Is_Super_Mod
}
puser.Is_Admin = puser.Is_Super_Admin
if puser.Avatar != "" {
if puser.Avatar[0] == '.' {
puser.Avatar = "/uploads/avatar_" + strconv.Itoa(puser.ID) + puser.Avatar
@ -409,7 +433,7 @@ func route_profile(w http.ResponseWriter, r *http.Request){
}
replyLines = strings.Count(replyContent,"\n")
if is_super_admin || groups[group].Is_Admin {
if is_super_admin || groups[group].Is_Mod || groups[group].Is_Admin {
replyCss = staff_css_tmpl
} else {
replyCss = no_css_tmpl

View File

@ -7,4 +7,9 @@
<a href="/topic/{{.ID}}">{{.Title}}</a> {{if .Is_Closed}}<span class="topic_status topic_status_closed">closed</span>{{else}}<span class="topic_status topic_status_open">open</span>{{end}}
</div>{{end}}
</div>
{{if .Something}}
<div class="rowblock">
<div class="rowitem passive">{{.Something}}</div>
</div>
{{end}}
{{template "footer.html" . }}

View File

@ -5,7 +5,8 @@
<li class="menu_topics"><a href="/">Topics</a></li>
<li class="menu_create_topic"><a href="/topics/create/">Create Topic</a></li>
{{ if .CurrentUser.Loggedin }}
<li class="menu_account"><a href="/user/edit/critical/">My Account</a></li>
<li class="menu_account"><a href="/user/edit/critical/">Account</a></li>
<li class="menu_account"><a href="/user/{{.CurrentUser.ID}}">Profile</a></li>
{{ if .CurrentUser.Is_Admin}}<li class="menu_account"><a href="/panel/forums/">Panel</a></li>{{end}}
<li class="menu_logout"><a href="/accounts/logout?session={{.CurrentUser.Session}}">Logout</a></li>
{{ else }}

View File

@ -4,7 +4,7 @@
<div class="rowitem">{{.Something.Name}}</div>
<div class="rowitem passive">
<a class="username">Add Friend</a>
{{if .CurrentUser.Is_Admin}}<a class="username">Ban</a>{{end}}
{{if (.CurrentUser.Is_Super_Mod) and not (.Something.Is_Super_Mod) }}<a class="username">Ban</a>{{end}}
<a class="username">Report</a>
</div>
</div>
@ -13,11 +13,11 @@
</div>
<div class="colblock_right" style="overflow: hidden;">
{{range $index, $element := .ItemList}}
<div class="rowitem passive deletable_block editable_parent" style="{{ if $element.Avatar }}background-image: url({{$element.Avatar}}), url(/static/white-dot.jpg);background-position: 0px {{if le $element.ContentLines 5}}-1{{end}}0px;background-repeat: no-repeat, repeat-y;background-size: 128px;background-attachment: scroll;padding-left: 136px;{{$element.Css}}{{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: 0px {{if le $element.ContentLines 5}}-1{{end}}0px;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 href="/user/{{$element.CreatedBy}}" class="username">{{$element.CreatedByName}}</a>
{{if $.CurrentUser.Is_Admin}}<a href="/reply/edit/submit/{{$element.ID}}"><button class="username edit_item">Edit</button></a>
{{if $.CurrentUser.Is_Mod}}<a href="/profile/reply/edit/submit/{{$element.ID}}"><button class="username edit_item">Edit</button></a>
<a href="/profile/reply/delete/submit/{{$element.ID}}"><button class="username delete_item">Delete</button></a>{{end}}
<a href="/profile/reply/report/submit/{{$element.ID}}"><button class="username report_item">Report</button></a>
{{ if $element.Tag }}<a class="username" style="float: right;">{{$element.Tag}}</a>{{end}}

View File

@ -4,7 +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}}
{{if .CurrentUser.Is_Mod}}
<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}}
@ -21,7 +21,7 @@
</form>
</div>
<div class="rowblock">
<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: 0px {{if le .Something.ContentLines 5}}-1{{end}}0px;background-repeat: no-repeat, repeat-y;background-size: 128px;background-attachment: scroll;padding-left: 136px;{{.Something.Css}}{{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: 0px {{if le .Something.ContentLines 5}}-1{{end}}0px;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 />
@ -31,11 +31,11 @@
</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}}), url(/static/white-dot.jpg);background-position: 0px {{if le $element.ContentLines 5}}-1{{end}}0px;background-repeat: no-repeat, repeat-y;background-size: 128px;background-attachment: scroll;padding-left: 136px;{{$element.Css}}{{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: 0px {{if le $element.ContentLines 5}}-1{{end}}0px;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 href="/user/{{$element.CreatedBy}}" class="username">{{$element.CreatedByName}}</a>
{{if $.CurrentUser.Is_Admin}}<a href="/reply/edit/submit/{{$element.ID}}"><button class="username edit_item">Edit</button></a>
{{if $.CurrentUser.Is_Mod}}<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}}
<a href="/reply/report/submit/{{$element.ID}}"><button class="username report_item">Report</button></a>
{{if $element.Tag}}<a class="username" style="float: right;">{{$element.Tag}}</a>{{end}}

View File

@ -7,4 +7,9 @@
<a href="/topic/{{.ID}}">{{.Title}}</a> {{if .Is_Closed}}<span class="topic_status topic_status_closed">closed</span>{{else}}<span class="topic_status topic_status_open">open</span>{{end}}
</div>{{end}}
</div>
{{if .Something}}
<div class="rowblock">
<div class="rowitem passive">{{.Something}}</div>
</div>
{{end}}
{{template "footer.html" . }}

24
user.go
View File

@ -12,6 +12,8 @@ type User struct
ID int
Name string
Group int
Is_Mod bool
Is_Super_Mod bool
Is_Admin bool
Is_Super_Admin bool
Is_Banned bool
@ -40,7 +42,7 @@ func SetPassword(uid int, password string) (error) {
}
func SessionCheck(w http.ResponseWriter, r *http.Request) (User) {
user := User{0,"",0,false,false,false,"",false,""}
user := User{0,"",0,false,false,false,false,false,"",false,""}
var err error
var cookie *http.Cookie
@ -60,8 +62,6 @@ func SessionCheck(w http.ResponseWriter, r *http.Request) (User) {
return user
}
user.Session = cookie.Value
//log.Print("ID: " + user.Name)
//log.Print("Session: " + user.Session)
// Is this session valid..?
err = get_session_stmt.QueryRow(user.ID,user.Session).Scan(&user.ID, &user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Avatar)
@ -71,7 +71,9 @@ func SessionCheck(w http.ResponseWriter, r *http.Request) (User) {
log.Print(err)
return user
}
user.Is_Admin = (user.Is_Super_Admin || groups[user.Group].Is_Admin)
user.Is_Admin = user.Is_Super_Admin || groups[user.Group].Is_Admin
user.Is_Super_Mod = groups[user.Group].Is_Mod || user.Is_Admin
user.Is_Mod = user.Is_Super_Mod
user.Is_Banned = groups[user.Group].Is_Banned
if user.Avatar != "" {
if user.Avatar[0] == '.' {
@ -81,19 +83,5 @@ func SessionCheck(w http.ResponseWriter, r *http.Request) (User) {
user.Avatar = strings.Replace(noavatar,"{id}",strconv.Itoa(user.ID),1)
}
user.Loggedin = true
/*log.Print("Logged in")
log.Print("ID: " + strconv.Itoa(user.ID))
log.Print("Group: " + strconv.Itoa(user.Group))
log.Print("Name: " + user.Name)
if user.Loggedin {
log.Print("Loggedin: true")
} else {
log.Print("Loggedin: false")
}
if user.Is_Admin {
log.Print("Is_Admin: true")
} else {
log.Print("Is_Admin: false")
}*/
return user
}