gosora/routes/profile.go
Azareal b9973719a5 Added a small reply cache.
Preloaded a small number of users and topics.
Use cache.Set instead of cache.Add in a few spots for topics to avoid issues with the counter falling out of sync with the cache length.
Embed Reply in ReplyUser instead of duplicating the same fields.
Add the missing AttachCount and ActionType fields to Reply.
Add the missing Poll field to TopicsRow.
Added the TopicUser.Replies method to better abstract the reply list generation logic.
Shortened some common.s to c.s
Moved memStuff out of the analytics memory panes and into analytics.js
Removed the temporary preStats fix for label overflow now that we have a better solution.
Added the panel_analytics_script_memory template to help de-dupe logic in the analytics memory panes.
Added the Topic method to TopicsRow.
Added the GetRidsForTopic method to help cache replies in a small set of scenarios.

Added the ReplyCache config.json setting.
Added the ReplyCacheCapacity config.json setting.

Added a parser test case.
Added more Reply and ReplyStore related test cases.
2019-05-17 18:40:41 +10:00

114 lines
3.5 KiB
Go

package routes
import (
"database/sql"
"net/http"
"time"
c "github.com/Azareal/Gosora/common"
"github.com/Azareal/Gosora/common/phrases"
"github.com/Azareal/Gosora/query_gen"
)
type ProfileStmts struct {
getReplies *sql.Stmt
}
var profileStmts ProfileStmts
// TODO: Move these DbInits into some sort of abstraction
func init() {
c.DbInits.Add(func(acc *qgen.Accumulator) error {
profileStmts = ProfileStmts{
getReplies: acc.SimpleLeftJoin("users_replies", "users", "users_replies.rid, users_replies.content, users_replies.createdBy, users_replies.createdAt, users_replies.lastEdit, users_replies.lastEditBy, users.avatar, users.name, users.group", "users_replies.createdBy = users.uid", "users_replies.uid = ?", "", ""),
}
return acc.FirstError()
})
}
// TODO: Remove the View part of the name?
func ViewProfile(w http.ResponseWriter, r *http.Request, user c.User, header *c.Header) c.RouteError {
// TODO: Preload this?
header.AddSheet(header.Theme.Name + "/profile.css")
if user.Loggedin {
header.AddScriptAsync("profile_member.js")
}
var err error
var replyCreatedAt time.Time
var replyContent, replyCreatedByName, replyAvatar string
var rid, replyCreatedBy, replyLastEdit, replyLastEditBy, replyGroup int
var replyList []*c.ReplyUser
// TODO: Do a 301 if it's the wrong username? Do a canonical too?
_, pid, err := ParseSEOURL(r.URL.Path[len("/user/"):])
if err != nil {
return c.LocalError("The provided UserID is not a valid number.", w, r, user)
}
var puser *c.User
if pid == user.ID {
user.IsMod = true
puser = &user
} else {
// Fetch the user data
// TODO: Add a shared function for checking for ErrNoRows and internal erroring if it's not that case?
puser, err = c.Users.Get(pid)
if err == sql.ErrNoRows {
return c.NotFound(w, r, header)
} else if err != nil {
return c.InternalError(err, w, r)
}
puser.Init()
}
header.Title = phrases.GetTitlePhrasef("profile", puser.Name)
header.Path = c.BuildProfileURL(c.NameToSlug(puser.Name), puser.ID)
// Get the replies..
rows, err := profileStmts.getReplies.Query(puser.ID)
if err != nil {
return c.InternalError(err, w, r)
}
defer rows.Close()
for rows.Next() {
err := rows.Scan(&rid, &replyContent, &replyCreatedBy, &replyCreatedAt, &replyLastEdit, &replyLastEditBy, &replyAvatar, &replyCreatedByName, &replyGroup)
if err != nil {
return c.InternalError(err, w, r)
}
replyLiked := false
replyLikeCount := 0
ru := &c.ReplyUser{Reply: c.Reply{rid, 0, replyContent, replyCreatedBy, replyGroup, replyCreatedAt, replyLastEdit, replyLastEditBy, 0, "", replyLiked, replyLikeCount, 0, ""}, ContentHtml: c.ParseMessage(replyContent, 0, ""), CreatedByName: replyCreatedByName, Avatar: replyAvatar, Level: 0}
ru.Init(puser.ID)
group, err := c.Groups.Get(ru.Group)
if err != nil {
return c.InternalError(err, w, r)
}
if group.Tag != "" {
ru.Tag = group.Tag
} else if puser.ID == ru.CreatedBy {
ru.Tag = phrases.GetTmplPhrase("profile_owner_tag")
} else {
ru.Tag = ""
}
// TODO: Add a hook here
replyList = append(replyList, ru)
}
err = rows.Err()
if err != nil {
return c.InternalError(err, w, r)
}
// Normalise the score so that the user sees their relative progress to the next level rather than showing them their total score
prevScore := c.GetLevelScore(puser.Level)
currentScore := puser.Score - prevScore
nextScore := c.GetLevelScore(puser.Level+1) - prevScore
ppage := c.ProfilePage{header, replyList, *puser, currentScore, nextScore}
return renderTemplate("profile", w, r, header, ppage)
}