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
|
# 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.
|
||||||
|
|
14
src/data.sql
14
src/data.sql
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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 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
|
||||||
|
|
154
src/routes.go
154
src/routes.go
|
@ -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}
|
||||||
|
|
|
@ -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
|
|
@ -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">
|
<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 }}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
34
src/utils.go
34
src/utils.go
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue