Added the forums.

Added the forum list.
Added the forum view.
Added a relative time utility function.
This commit is contained in:
Azareal 2016-12-03 13:45:08 +00:00
parent 82b351e1d7
commit 771af02674
11 changed files with 242 additions and 12 deletions

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 config.go go run errors.go main.go pages.go reply.go routes.go topic.go user.go utils.go forum.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.
@ -45,8 +45,6 @@ Oh my, you caught me right at the start of this project. There's nothing to see
More moderation features. More moderation features.
Fix the bug where errors are sent off in raw HTML rather than formatted HTML.
Fix the custom pages. Fix the custom pages.
Add emails as a requirement for registration and add a simple anti-spam measure. Add emails as a requirement for registration and add a simple anti-spam measure.
@ -64,3 +62,7 @@ Add a plugin system.
Revamp the system for serving static files to make it much faster. Revamp the system for serving static files to make it much faster.
Tweak the CSS to make it responsive. Tweak the CSS to make it responsive.
Add a forum cache.
Add a group cache.

View File

@ -24,6 +24,17 @@ CREATE TABLE `users_groups`(
primary key(`gid`) primary key(`gid`)
); );
CREATE TABLE `forums`(
`fid` int not null AUTO_INCREMENT,
`name` varchar(100) not null,
`lastTopic` varchar(100) DEFAULT '' not null,
`lastTopicID` int DEFAULT 0 not null,
`lastReplyer` varchar(100) DEFAULT '' not null,
`lastReplyerID` int DEFAULT 0 not null,
`lastTopicTime` datetime not null,
primary key(`fid`)
);
CREATE TABLE `topics`( CREATE TABLE `topics`(
`tid` int not null AUTO_INCREMENT, `tid` int not null AUTO_INCREMENT,
`title` varchar(100) not null, `title` varchar(100) not null,
@ -34,7 +45,7 @@ CREATE TABLE `topics`(
`createdBy` int not null, `createdBy` int not null,
`is_closed` tinyint DEFAULT 0 not null, `is_closed` tinyint DEFAULT 0 not null,
`sticky` tinyint DEFAULT 0 not null, `sticky` tinyint DEFAULT 0 not null,
`parentID` int DEFAULT 0 not null, `parentID` int DEFAULT 1 not null,
primary key(`tid`) primary key(`tid`)
); );
@ -53,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 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);

12
src/forum.go Normal file
View File

@ -0,0 +1,12 @@
package main
type Forum struct
{
ID int
Name string
LastTopic string
LastTopicID int
LastReplyer string
LastReplyerID int
LastTopicTime string
}

View File

@ -21,6 +21,7 @@ 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
var create_reply_stmt *sql.Stmt var create_reply_stmt *sql.Stmt
var update_forum_cache_stmt *sql.Stmt
var edit_topic_stmt *sql.Stmt var edit_topic_stmt *sql.Stmt
var edit_reply_stmt *sql.Stmt var edit_reply_stmt *sql.Stmt
var delete_reply_stmt *sql.Stmt var delete_reply_stmt *sql.Stmt
@ -72,6 +73,12 @@ func init_database(err error) {
log.Fatal(err) log.Fatal(err)
} }
log.Print("Preparing update_forum_cache statement.")
update_forum_cache_stmt, err = db.Prepare("UPDATE forums SET lastTopic = ?, lastTopicID = ?, lastReplyer = ?, lastReplyerID = ?, lastTopicTime = NOW() WHERE fid = ?")
if err != nil {
log.Fatal(err)
}
log.Print("Preparing edit_topic statement.") log.Print("Preparing edit_topic statement.")
edit_topic_stmt, err = db.Prepare("UPDATE topics SET title = ?, content = ?, parsed_content = ?, is_closed = ? WHERE tid = ?") edit_topic_stmt, err = db.Prepare("UPDATE topics SET title = ?, content = ?, parsed_content = ?, is_closed = ? WHERE tid = ?")
if err != nil { if err != nil {
@ -185,6 +192,8 @@ func main(){
http.HandleFunc("/overview/", route_overview) http.HandleFunc("/overview/", route_overview)
http.HandleFunc("/topics/create/", route_topic_create) http.HandleFunc("/topics/create/", route_topic_create)
http.HandleFunc("/topics/", route_topics) http.HandleFunc("/topics/", route_topics)
http.HandleFunc("/forums/", route_forums)
http.HandleFunc("/forum/", route_forum)
http.HandleFunc("/topic/create/submit/", route_create_topic) //POST http.HandleFunc("/topic/create/submit/", route_create_topic) //POST
http.HandleFunc("/topic/", route_topic_id) http.HandleFunc("/topic/", route_topic_id)
http.HandleFunc("/reply/create/", route_create_reply) //POST http.HandleFunc("/reply/create/", route_create_reply) //POST

View File

@ -21,9 +21,6 @@ var tList map[int]interface{}
// GET functions // GET functions
func route_overview(w http.ResponseWriter, r *http.Request){ func route_overview(w http.ResponseWriter, r *http.Request){
user := SessionCheck(w,r) user := SessionCheck(w,r)
NoPermissions(w, r, user)
return
pi := Page{"Overview","overview",user,tList,0} pi := Page{"Overview","overview",user,tList,0}
err := templates.ExecuteTemplate(w,"overview.html", pi) err := templates.ExecuteTemplate(w,"overview.html", pi)
if err != nil { if err != nil {
@ -109,6 +106,129 @@ func route_topics(w http.ResponseWriter, r *http.Request){
InternalError(err, w, r, user) InternalError(err, w, r, user)
} }
} }
func route_forum(w http.ResponseWriter, r *http.Request){
user := SessionCheck(w,r)
var(
topicList map[int]interface{}
currentID int
fname string
tid int
title string
content string
createdBy int
is_closed bool
sticky bool
createdAt string
parentID int
status string
name string
avatar string
)
topicList = make(map[int]interface{})
currentID = 0
fid, err := strconv.Atoi(r.URL.Path[len("/forum/"):])
if err != nil {
LocalError("The provided ForumID is not a valid number.",w,r,user)
return
}
err = db.QueryRow("select name from forums where fid = ?", fid).Scan(&fname)
if err == sql.ErrNoRows {
pi := Page{"Error","error",user,tList,"The requested forum doesn't exist."}
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
}
rows, err := db.Query("select topics.tid, topics.title, topics.content, topics.createdBy, topics.is_closed, topics.sticky, topics.createdAt, topics.parentID, users.name, users.avatar from topics left join users ON topics.createdBy = users.uid WHERE topics.parentID = ? order by topics.sticky DESC, topics.lastReplyAt DESC, topics.createdBy DESC", fid)
if err != nil {
InternalError(err,w,r,user)
return
}
defer rows.Close()
for rows.Next() {
err := rows.Scan(&tid, &title, &content, &createdBy, &is_closed, &sticky, &createdAt, &parentID, &name, &avatar)
if err != nil {
InternalError(err,w,r,user)
return
}
if is_closed {
status = "closed"
} else {
status = "open"
}
if avatar != "" && avatar[0] == '.' {
avatar = "/uploads/avatar_" + strconv.Itoa(createdBy) + avatar
}
topicList[currentID] = TopicUser{tid, title, content, createdBy, is_closed, sticky, createdAt,parentID, status, name, avatar}
currentID++
}
err = rows.Err()
if err != nil {
InternalError(err,w,r,user)
return
}
pi := Page{fname,"forum",user,topicList,0}
err = templates.ExecuteTemplate(w,"forum.html", pi)
if err != nil {
InternalError(err, w, r, user)
}
}
func route_forums(w http.ResponseWriter, r *http.Request){
user := SessionCheck(w,r)
var forumList map[int]interface{}
forumList = make(map[int]interface{})
currentID := 0
rows, err := db.Query("select fid, name, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime from forums")
if err != nil {
InternalError(err,w,r,user)
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
}
forum.LastTopicTime, err = relative_time(forum.LastTopicTime)
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 List","forums",user,forumList,0}
err = templates.ExecuteTemplate(w,"forums.html", pi)
if err != nil {
InternalError(err, w, r, user)
}
}
func route_topic_id(w http.ResponseWriter, r *http.Request){ func route_topic_id(w http.ResponseWriter, r *http.Request){
user := SessionCheck(w,r) user := SessionCheck(w,r)
@ -221,8 +341,9 @@ func route_create_topic(w http.ResponseWriter, r *http.Request) {
return return
} }
success := 1 success := 1
topic_name := html.EscapeString(r.PostFormValue("topic-name"))
res, err := create_topic_stmt.Exec(html.EscapeString(r.PostFormValue("topic-name")),html.EscapeString(r.PostFormValue("topic-content")),parse_message(html.EscapeString(r.PostFormValue("topic-content"))),user.ID) res, err := create_topic_stmt.Exec(topic_name,html.EscapeString(r.PostFormValue("topic-content")),parse_message(html.EscapeString(r.PostFormValue("topic-content"))),user.ID)
if err != nil { if err != nil {
log.Print(err) log.Print(err)
success = 0 success = 0
@ -234,6 +355,12 @@ func route_create_topic(w http.ResponseWriter, r *http.Request) {
success = 0 success = 0
} }
_, err = update_forum_cache_stmt.Exec(topic_name, lastId, user.Name, user.ID, 1)
if err != nil {
InternalError(err,w,r,user)
return
}
if success != 1 { if success != 1 {
errmsg := "Unable to create the topic" errmsg := "Unable to create the topic"
pi := Page{"Error","error",user,tList,errmsg} pi := Page{"Error","error",user,tList,errmsg}
@ -249,7 +376,6 @@ func route_create_topic(w http.ResponseWriter, r *http.Request) {
} }
func route_create_reply(w http.ResponseWriter, r *http.Request) { func route_create_reply(w http.ResponseWriter, r *http.Request) {
var tid int
user := SessionCheck(w,r) user := SessionCheck(w,r)
if !user.Loggedin { if !user.Loggedin {
LoginRequired(w,r,user) LoginRequired(w,r,user)
@ -263,7 +389,7 @@ func route_create_reply(w http.ResponseWriter, r *http.Request) {
} }
success := 1 success := 1
tid, err = strconv.Atoi(r.PostFormValue("tid")) tid, err := strconv.Atoi(r.PostFormValue("tid"))
if err != nil { if err != nil {
log.Print(err) log.Print(err)
success = 0 success = 0
@ -285,6 +411,22 @@ func route_create_reply(w http.ResponseWriter, r *http.Request) {
success = 0 success = 0
} }
var topic_name string
err = db.QueryRow("select title from topics where tid = ?", tid).Scan(&topic_name)
if err == sql.ErrNoRows {
log.Print(err)
success = 0
} else if err != nil {
InternalError(err,w,r,user)
return
}
_, err = update_forum_cache_stmt.Exec(topic_name, tid, user.Name, user.ID, 1)
if err != nil {
InternalError(err,w,r,user)
return
}
if success != 1 { if success != 1 {
errmsg := "Unable to create the reply" errmsg := "Unable to create the reply"
pi := Page{"Error","error",user,tList,errmsg} pi := Page{"Error","error",user,tList,errmsg}

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 go run errors.go main.go pages.go reply.go routes.go topic.go user.go utils.go config.go forum.go
pause pause

10
src/templates/forum.html Normal file
View File

@ -0,0 +1,10 @@
{{template "header.html" . }}
<div class="rowblock">
<div class="rowitem"><a>{{ .Title }}</a></div>
</div>
<div class="rowblock">
{{range .ItemList}}<div class="rowitem passive" style="{{ if .Avatar }}background-image: url({{ .Avatar }});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}{{ if .Sticky }}background-color: #FFFFCC;{{end}}">
<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>
{{template "footer.html" . }}

View File

@ -0,0 +1,8 @@
{{template "header.html" . }}
<div class="rowblock">
{{range .ItemList}}<div class="rowitem">
<a href="/forum/{{.ID}}">{{.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>
{{template "footer.html" . }}

View File

@ -1,6 +1,7 @@
<div class="nav"> <div class="nav">
<ul> <ul>
<li class="menu_overview"><a href="/">Overview</a></li> <li class="menu_overview"><a href="/">Overview</a></li>
<li class="menu_forums"><a href="/forums/">Forums</a></li>
<li class="menu_topics"><a href="/">Topics</a></li> <li class="menu_topics"><a href="/">Topics</a></li>
<li class="menu_create_topic"><a href="/topics/create/">Create Topic</a></li> <li class="menu_create_topic"><a href="/topics/create/">Create Topic</a></li>
{{ if .CurrentUser.Loggedin }} {{ if .CurrentUser.Loggedin }}

View File

@ -64,7 +64,7 @@ func SessionCheck(w http.ResponseWriter, r *http.Request) (User) {
// Is this session valid..? // 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) err = get_session_stmt.QueryRow(user.ID,user.Session).Scan(&user.ID, &user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Avatar)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
log.Print("Couldn't find the user session") //log.Print("Couldn't find the user session")
return user return user
} else if err != nil { } else if err != nil {
log.Print(err) log.Print(err)

View File

@ -1,4 +1,6 @@
package main package main
import "fmt"
import "time"
import "encoding/base64" import "encoding/base64"
import "crypto/rand" import "crypto/rand"
@ -10,4 +12,36 @@ func GenerateSafeString(length int) (string, error) {
return "", err return "", err
} }
return base64.URLEncoding.EncodeToString(rb), nil return base64.URLEncoding.EncodeToString(rb), nil
}
func relative_time(in string) (string, error) {
layout := "2006-01-02 15:04:05"
t, err := time.Parse(layout, in)
if err != nil {
return "", err
}
diff := time.Since(t)
hours := diff.Hours()
seconds := diff.Seconds()
switch {
case (hours / 24) > 7:
return t.Format("Mon Jan 2 2006"), err
case int(hours / 24) == 1:
return "1 day ago", err
case int(hours / 24) > 1:
return fmt.Sprintf("%d days ago", int(hours / 24)), err
case seconds <= 1:
return "a moment ago", err
case seconds < 60:
return fmt.Sprintf("%d seconds ago", int(seconds)), err
case seconds < 120:
return "a minute ago", err
case seconds < 3600:
return fmt.Sprintf("%d minutes ago", int(seconds / 60)), err
case seconds < 7200:
return "an hour ago", err
default:
return fmt.Sprintf("%d hours ago", int(seconds / 60 / 60)), err
}
} }