Added the forums.
Added the forum list. Added the forum view. Added a relative time utility function.
This commit is contained in:
parent
82b351e1d7
commit
771af02674
|
@ -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 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.
|
||||
|
||||
|
@ -45,8 +45,6 @@ Oh my, you caught me right at the start of this project. There's nothing to see
|
|||
|
||||
More moderation features.
|
||||
|
||||
Fix the bug where errors are sent off in raw HTML rather than formatted HTML.
|
||||
|
||||
Fix the custom pages.
|
||||
|
||||
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.
|
||||
|
||||
Tweak the CSS to make it responsive.
|
||||
|
||||
Add a forum cache.
|
||||
|
||||
Add a group cache.
|
||||
|
|
14
src/data.sql
14
src/data.sql
|
@ -24,6 +24,17 @@ CREATE TABLE `users_groups`(
|
|||
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`(
|
||||
`tid` int not null AUTO_INCREMENT,
|
||||
`title` varchar(100) not null,
|
||||
|
@ -34,7 +45,7 @@ CREATE TABLE `topics`(
|
|||
`createdBy` int not null,
|
||||
`is_closed` 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`)
|
||||
);
|
||||
|
||||
|
@ -53,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 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,12 @@
|
|||
package main
|
||||
|
||||
type Forum struct
|
||||
{
|
||||
ID int
|
||||
Name string
|
||||
LastTopic string
|
||||
LastTopicID int
|
||||
LastReplyer string
|
||||
LastReplyerID int
|
||||
LastTopicTime string
|
||||
}
|
|
@ -21,6 +21,7 @@ var db *sql.DB
|
|||
var get_session_stmt *sql.Stmt
|
||||
var create_topic_stmt *sql.Stmt
|
||||
var create_reply_stmt *sql.Stmt
|
||||
var update_forum_cache_stmt *sql.Stmt
|
||||
var edit_topic_stmt *sql.Stmt
|
||||
var edit_reply_stmt *sql.Stmt
|
||||
var delete_reply_stmt *sql.Stmt
|
||||
|
@ -72,6 +73,12 @@ func init_database(err error) {
|
|||
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.")
|
||||
edit_topic_stmt, err = db.Prepare("UPDATE topics SET title = ?, content = ?, parsed_content = ?, is_closed = ? WHERE tid = ?")
|
||||
if err != nil {
|
||||
|
@ -185,6 +192,8 @@ func main(){
|
|||
http.HandleFunc("/overview/", route_overview)
|
||||
http.HandleFunc("/topics/create/", route_topic_create)
|
||||
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/", route_topic_id)
|
||||
http.HandleFunc("/reply/create/", route_create_reply) //POST
|
||||
|
|
154
src/routes.go
154
src/routes.go
|
@ -21,9 +21,6 @@ var tList map[int]interface{}
|
|||
// GET functions
|
||||
func route_overview(w http.ResponseWriter, r *http.Request){
|
||||
user := SessionCheck(w,r)
|
||||
NoPermissions(w, r, user)
|
||||
return
|
||||
|
||||
pi := Page{"Overview","overview",user,tList,0}
|
||||
err := templates.ExecuteTemplate(w,"overview.html", pi)
|
||||
if err != nil {
|
||||
|
@ -109,6 +106,129 @@ func route_topics(w http.ResponseWriter, r *http.Request){
|
|||
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){
|
||||
user := SessionCheck(w,r)
|
||||
|
@ -221,8 +341,9 @@ func route_create_topic(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
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 {
|
||||
log.Print(err)
|
||||
success = 0
|
||||
|
@ -234,6 +355,12 @@ func route_create_topic(w http.ResponseWriter, r *http.Request) {
|
|||
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 {
|
||||
errmsg := "Unable to create the topic"
|
||||
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) {
|
||||
var tid int
|
||||
user := SessionCheck(w,r)
|
||||
if !user.Loggedin {
|
||||
LoginRequired(w,r,user)
|
||||
|
@ -263,7 +389,7 @@ func route_create_reply(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
success := 1
|
||||
tid, err = strconv.Atoi(r.PostFormValue("tid"))
|
||||
tid, err := strconv.Atoi(r.PostFormValue("tid"))
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
success = 0
|
||||
|
@ -285,6 +411,22 @@ func route_create_reply(w http.ResponseWriter, r *http.Request) {
|
|||
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 {
|
||||
errmsg := "Unable to create the reply"
|
||||
pi := Page{"Error","error",user,tList,errmsg}
|
||||
|
|
|
@ -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
|
|
@ -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" . }}
|
|
@ -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" . }}
|
|
@ -1,6 +1,7 @@
|
|||
<div class="nav">
|
||||
<ul>
|
||||
<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_create_topic"><a href="/topics/create/">Create Topic</a></li>
|
||||
{{ if .CurrentUser.Loggedin }}
|
||||
|
|
|
@ -64,7 +64,7 @@ func SessionCheck(w http.ResponseWriter, r *http.Request) (User) {
|
|||
// 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)
|
||||
if err == sql.ErrNoRows {
|
||||
log.Print("Couldn't find the user session")
|
||||
//log.Print("Couldn't find the user session")
|
||||
return user
|
||||
} else if err != nil {
|
||||
log.Print(err)
|
||||
|
|
34
src/utils.go
34
src/utils.go
|
@ -1,4 +1,6 @@
|
|||
package main
|
||||
import "fmt"
|
||||
import "time"
|
||||
import "encoding/base64"
|
||||
import "crypto/rand"
|
||||
|
||||
|
@ -10,4 +12,36 @@ func GenerateSafeString(length int) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue