Shorten the names of the IP fields.

Shorten some other things.
This commit is contained in:
Azareal 2019-09-01 08:34:43 +10:00
parent 31d65b91a6
commit f1bebb7326
27 changed files with 339 additions and 340 deletions

View File

@ -163,7 +163,7 @@ type ESTopic struct {
Title string `json:"title"`
Content string `json:"content"`
CreatedBy int `json:"createdBy"`
IPAddress string `json:"ip"`
IP string `json:"ip"`
}
type ESReply struct {
@ -171,7 +171,7 @@ type ESReply struct {
TID int `json:"tid"`
Content string `json:"content"`
CreatedBy int `json:"createdBy"`
IPAddress string `json:"ip"`
IP string `json:"ip"`
}
func setupData(client *elastic.Client) error {
@ -198,12 +198,12 @@ func setupData(client *elastic.Client) error {
oi := 0
err := qgen.NewAcc().Select("topics").Cols("tid, title, content, createdBy, ipaddress").Each(func(rows *sql.Rows) error {
topic := ESTopic{}
err := rows.Scan(&topic.ID, &topic.Title, &topic.Content, &topic.CreatedBy, &topic.IPAddress)
t := ESTopic{}
err := rows.Scan(&t.ID, &t.Title, &t.Content, &t.CreatedBy, &t.IP)
if err != nil {
return err
}
tin[oi] <- topic
tin[oi] <- t
if oi < 3 {
oi++
}
@ -234,12 +234,12 @@ func setupData(client *elastic.Client) error {
}
oi := 0
err := qgen.NewAcc().Select("replies").Cols("rid, tid, content, createdBy, ipaddress").Each(func(rows *sql.Rows) error {
reply := ESReply{}
err := rows.Scan(&reply.ID, &reply.TID, &reply.Content, &reply.CreatedBy, &reply.IPAddress)
r := ESReply{}
err := rows.Scan(&r.ID, &r.TID, &r.Content, &r.CreatedBy, &r.IP)
if err != nil {
return err
}
rin[oi] <- reply
rin[oi] <- r
if oi < 3 {
oi++
}

View File

@ -14,13 +14,13 @@ type LogItem struct {
Action string
ElementID int
ElementType string
IPAddress string
IP string
ActorID int
DoneAt string
}
type LogStore interface {
Create(action string, elementID int, elementType string, ipaddress string, actorID int) (err error)
Create(action string, elementID int, elementType string, ip string, actorID int) (err error)
Count() int
GetOffset(offset int, perPage int) (logs []LogItem, err error)
}
@ -40,8 +40,8 @@ func NewModLogStore(acc *qgen.Accumulator) (*SQLModLogStore, error) {
}
// TODO: Make a store for this?
func (s *SQLModLogStore) Create(action string, elementID int, elementType string, ipaddress string, actorID int) (err error) {
_, err = s.create.Exec(action, elementID, elementType, ipaddress, actorID)
func (s *SQLModLogStore) Create(action string, elementID int, elementType string, ip string, actorID int) (err error) {
_, err = s.create.Exec(action, elementID, elementType, ip, actorID)
return err
}
@ -55,20 +55,20 @@ func (s *SQLModLogStore) Count() (count int) {
func buildLogList(rows *sql.Rows) (logs []LogItem, err error) {
for rows.Next() {
var log LogItem
var l LogItem
var doneAt time.Time
err := rows.Scan(&log.Action, &log.ElementID, &log.ElementType, &log.IPAddress, &log.ActorID, &doneAt)
err := rows.Scan(&l.Action, &l.ElementID, &l.ElementType, &l.IP, &l.ActorID, &doneAt)
if err != nil {
return logs, err
}
log.DoneAt = doneAt.Format("2006-01-02 15:04:05")
logs = append(logs, log)
l.DoneAt = doneAt.Format("2006-01-02 15:04:05")
logs = append(logs, l)
}
return logs, rows.Err()
}
func (store *SQLModLogStore) GetOffset(offset int, perPage int) (logs []LogItem, err error) {
rows, err := store.getOffset.Query(offset, perPage)
func (s *SQLModLogStore) GetOffset(offset int, perPage int) (logs []LogItem, err error) {
rows, err := s.getOffset.Query(offset, perPage)
if err != nil {
return logs, err
}
@ -91,8 +91,8 @@ func NewAdminLogStore(acc *qgen.Accumulator) (*SQLAdminLogStore, error) {
}
// TODO: Make a store for this?
func (s *SQLAdminLogStore) Create(action string, elementID int, elementType string, ipaddress string, actorID int) (err error) {
_, err = s.create.Exec(action, elementID, elementType, ipaddress, actorID)
func (s *SQLAdminLogStore) Create(action string, elementID int, elementType string, ip string, actorID int) (err error) {
_, err = s.create.Exec(action, elementID, elementType, ip, actorID)
return err
}

View File

@ -68,59 +68,59 @@ func init() {
}
// Copy gives you a non-pointer concurrency safe copy of the forum
func (forum *Forum) Copy() (fcopy Forum) {
fcopy = *forum
func (f *Forum) Copy() (fcopy Forum) {
fcopy = *f
return fcopy
}
// TODO: Write tests for this
func (forum *Forum) Update(name string, desc string, active bool, preset string) error {
func (f *Forum) Update(name string, desc string, active bool, preset string) error {
if name == "" {
name = forum.Name
name = f.Name
}
// TODO: Do a line sanitise? Does it matter?
preset = strings.TrimSpace(preset)
_, err := forumStmts.update.Exec(name, desc, active, preset, forum.ID)
_, err := forumStmts.update.Exec(name, desc, active, preset, f.ID)
if err != nil {
return err
}
if forum.Preset != preset && preset != "custom" && preset != "" {
err = PermmapToQuery(PresetToPermmap(preset), forum.ID)
if f.Preset != preset && preset != "custom" && preset != "" {
err = PermmapToQuery(PresetToPermmap(preset), f.ID)
if err != nil {
return err
}
}
_ = Forums.Reload(forum.ID)
_ = Forums.Reload(f.ID)
return nil
}
func (forum *Forum) SetPreset(preset string, gid int) error {
func (f *Forum) SetPreset(preset string, gid int) error {
fperms, changed := GroupForumPresetToForumPerms(preset)
if changed {
return forum.SetPerms(fperms, preset, gid)
return f.SetPerms(fperms, preset, gid)
}
return nil
}
// TODO: Refactor this
func (forum *Forum) SetPerms(fperms *ForumPerms, preset string, gid int) (err error) {
err = ReplaceForumPermsForGroup(gid, map[int]string{forum.ID: preset}, map[int]*ForumPerms{forum.ID: fperms})
func (f *Forum) SetPerms(fperms *ForumPerms, preset string, gid int) (err error) {
err = ReplaceForumPermsForGroup(gid, map[int]string{f.ID: preset}, map[int]*ForumPerms{f.ID: fperms})
if err != nil {
LogError(err)
return errors.New("Unable to update the permissions")
}
// TODO: Add this and replaceForumPermsForGroup into a transaction?
_, err = forumStmts.setPreset.Exec("", forum.ID)
_, err = forumStmts.setPreset.Exec("", f.ID)
if err != nil {
LogError(err)
return errors.New("Unable to update the forum")
}
err = Forums.Reload(forum.ID)
err = Forums.Reload(f.ID)
if err != nil {
return errors.New("Unable to reload forum")
}
err = FPStore.Reload(forum.ID)
err = FPStore.Reload(f.ID)
if err != nil {
return errors.New("Unable to reload the forum permissions")
}

View File

@ -53,45 +53,45 @@ func init() {
})
}
func (group *Group) ChangeRank(isAdmin bool, isMod bool, isBanned bool) (err error) {
_, err = groupStmts.updateGroupRank.Exec(isAdmin, isMod, isBanned, group.ID)
func (g *Group) ChangeRank(isAdmin bool, isMod bool, isBanned bool) (err error) {
_, err = groupStmts.updateGroupRank.Exec(isAdmin, isMod, isBanned, g.ID)
if err != nil {
return err
}
Groups.Reload(group.ID)
Groups.Reload(g.ID)
return nil
}
func (group *Group) Update(name string, tag string) (err error) {
_, err = groupStmts.updateGroup.Exec(name, tag, group.ID)
func (g *Group) Update(name string, tag string) (err error) {
_, err = groupStmts.updateGroup.Exec(name, tag, g.ID)
if err != nil {
return err
}
Groups.Reload(group.ID)
Groups.Reload(g.ID)
return nil
}
// Please don't pass arbitrary inputs to this method
func (group *Group) UpdatePerms(perms map[string]bool) (err error) {
func (g *Group) UpdatePerms(perms map[string]bool) (err error) {
pjson, err := json.Marshal(perms)
if err != nil {
return err
}
_, err = groupStmts.updateGroupPerms.Exec(pjson, group.ID)
_, err = groupStmts.updateGroupPerms.Exec(pjson, g.ID)
if err != nil {
return err
}
return Groups.Reload(group.ID)
return Groups.Reload(g.ID)
}
// Copy gives you a non-pointer concurrency safe copy of the group
func (group *Group) Copy() Group {
return *group
func (g *Group) Copy() Group {
return *g
}
func (group *Group) CopyPtr() (co *Group) {
func (g *Group) CopyPtr() (co *Group) {
co = new(Group)
*co = *group
*co = *g
return co
}

View File

@ -22,7 +22,7 @@ type GroupStore interface {
Get(id int) (*Group, error)
GetCopy(id int) (Group, error)
Exists(id int) bool
Create(name string, tag string, isAdmin bool, isMod bool, isBanned bool) (int, error)
Create(name string, tag string, isAdmin bool, isMod bool, isBanned bool) (id int, err error)
GetAll() ([]*Group, error)
GetRange(lower int, higher int) ([]*Group, error)
Reload(id int) error // ? - Should we move this to GroupCache? It might require us to do some unnecessary casting though

View File

@ -16,7 +16,7 @@ type RegLogItem struct {
Email string
FailureReason string
Success bool
IPAddress string
IP string
DoneAt string
}
@ -39,19 +39,19 @@ func init() {
// TODO: Reload this item in the store, probably doesn't matter right now, but it might when we start caching this stuff in memory
// ! Retroactive updates of date are not permitted for integrity reasons
func (log *RegLogItem) Commit() error {
_, err := regLogStmts.update.Exec(log.Username, log.Email, log.FailureReason, log.Success, log.ID)
func (l *RegLogItem) Commit() error {
_, err := regLogStmts.update.Exec(l.Username, l.Email, l.FailureReason, l.Success, l.ID)
return err
}
func (log *RegLogItem) Create() (id int, err error) {
res, err := regLogStmts.create.Exec(log.Username, log.Email, log.FailureReason, log.Success, log.IPAddress)
func (l *RegLogItem) Create() (id int, err error) {
res, err := regLogStmts.create.Exec(l.Username, l.Email, l.FailureReason, l.Success, l.IP)
if err != nil {
return 0, err
}
id64, err := res.LastInsertId()
log.ID = int(id64)
return log.ID, err
l.ID = int(id64)
return l.ID, err
}
type RegLogStore interface {
@ -79,22 +79,22 @@ func (s *SQLRegLogStore) Count() (count int) {
return count
}
func (store *SQLRegLogStore) GetOffset(offset int, perPage int) (logs []RegLogItem, err error) {
rows, err := store.getOffset.Query(offset, perPage)
func (s *SQLRegLogStore) GetOffset(offset int, perPage int) (logs []RegLogItem, err error) {
rows, err := s.getOffset.Query(offset, perPage)
if err != nil {
return logs, err
}
defer rows.Close()
for rows.Next() {
var log RegLogItem
var l RegLogItem
var doneAt time.Time
err := rows.Scan(&log.ID, &log.Username, &log.Email, &log.FailureReason, &log.Success, &log.IPAddress, &doneAt)
err := rows.Scan(&l.ID, &l.Username, &l.Email, &l.FailureReason, &l.Success, &l.IP, &doneAt)
if err != nil {
return logs, err
}
log.DoneAt = doneAt.Format("2006-01-02 15:04:05")
logs = append(logs, log)
l.DoneAt = doneAt.Format("2006-01-02 15:04:05")
logs = append(logs, l)
}
return logs, rows.Err()
}
@ -103,7 +103,7 @@ type LoginLogItem struct {
ID int
UID int
Success bool
IPAddress string
IP string
DoneAt string
}
@ -126,19 +126,19 @@ func init() {
// TODO: Reload this item in the store, probably doesn't matter right now, but it might when we start caching this stuff in memory
// ! Retroactive updates of date are not permitted for integrity reasons
func (log *LoginLogItem) Commit() error {
_, err := loginLogStmts.update.Exec(log.UID, log.Success, log.ID)
func (l *LoginLogItem) Commit() error {
_, err := loginLogStmts.update.Exec(l.UID, l.Success, l.ID)
return err
}
func (log *LoginLogItem) Create() (id int, err error) {
res, err := loginLogStmts.create.Exec(log.UID, log.Success, log.IPAddress)
func (l *LoginLogItem) Create() (id int, err error) {
res, err := loginLogStmts.create.Exec(l.UID, l.Success, l.IP)
if err != nil {
return 0, err
}
id64, err := res.LastInsertId()
log.ID = int(id64)
return log.ID, err
l.ID = int(id64)
return l.ID, err
}
type LoginLogStore interface {
@ -177,22 +177,22 @@ func (s *SQLLoginLogStore) CountUser(uid int) (count int) {
return count
}
func (store *SQLLoginLogStore) GetOffset(uid int, offset int, perPage int) (logs []LoginLogItem, err error) {
rows, err := store.getOffsetByUser.Query(uid, offset, perPage)
func (s *SQLLoginLogStore) GetOffset(uid int, offset int, perPage int) (logs []LoginLogItem, err error) {
rows, err := s.getOffsetByUser.Query(uid, offset, perPage)
if err != nil {
return logs, err
}
defer rows.Close()
for rows.Next() {
var log = LoginLogItem{UID: uid}
l := LoginLogItem{UID: uid}
var doneAt time.Time
err := rows.Scan(&log.ID, &log.Success, &log.IPAddress, &doneAt)
err := rows.Scan(&l.ID, &l.Success, &l.IP, &doneAt)
if err != nil {
return logs, err
}
log.DoneAt = doneAt.Format("2006-01-02 15:04:05")
logs = append(logs, log)
l.DoneAt = doneAt.Format("2006-01-02 15:04:05")
logs = append(logs, l)
}
return logs, rows.Err()
}

View File

@ -590,7 +590,7 @@ type PanelBackupPage struct {
type PageLogItem struct {
Action template.HTML
IPAddress string
IP string
DoneAt string
}

View File

@ -207,18 +207,18 @@ func OverridePerms(perms *Perms, status bool) {
}
// TODO: We need a better way of overriding forum perms rather than setting them one by one
func OverrideForumPerms(perms *Perms, status bool) {
perms.ViewTopic = status
perms.LikeItem = status
perms.CreateTopic = status
perms.EditTopic = status
perms.DeleteTopic = status
perms.CreateReply = status
perms.EditReply = status
perms.DeleteReply = status
perms.PinTopic = status
perms.CloseTopic = status
perms.MoveTopic = status
func OverrideForumPerms(p *Perms, status bool) {
p.ViewTopic = status
p.LikeItem = status
p.CreateTopic = status
p.EditTopic = status
p.DeleteTopic = status
p.CreateReply = status
p.EditReply = status
p.DeleteReply = status
p.PinTopic = status
p.CloseTopic = status
p.MoveTopic = status
}
func RegisterPluginPerm(name string) {

View File

@ -20,7 +20,7 @@ type ProfileReply struct {
LastEdit int
LastEditBy int
ContentLines int
IPAddress string
IP string
}
type ProfileReplyStmts struct {
@ -44,18 +44,18 @@ func BlankProfileReply(id int) *ProfileReply {
}
// TODO: Write tests for this
func (reply *ProfileReply) Delete() error {
_, err := profileReplyStmts.delete.Exec(reply.ID)
func (r *ProfileReply) Delete() error {
_, err := profileReplyStmts.delete.Exec(r.ID)
return err
}
func (reply *ProfileReply) SetBody(content string) error {
func (r *ProfileReply) SetBody(content string) error {
content = PreparseMessage(html.UnescapeString(content))
_, err := profileReplyStmts.edit.Exec(content, ParseMessage(content, 0, ""), reply.ID)
_, err := profileReplyStmts.edit.Exec(content, ParseMessage(content, 0, ""), r.ID)
return err
}
// TODO: We can get this from the topic store instead of a query which will always miss the cache...
func (reply *ProfileReply) Creator() (*User, error) {
return Users.Get(reply.CreatedBy)
func (r *ProfileReply) Creator() (*User, error) {
return Users.Get(r.CreatedBy)
}

View File

@ -32,7 +32,7 @@ func NewSQLProfileReplyStore(acc *qgen.Accumulator) (*SQLProfileReplyStore, erro
func (s *SQLProfileReplyStore) Get(id int) (*ProfileReply, error) {
r := ProfileReply{ID: id}
err := s.get.QueryRow(id).Scan(&r.ParentID, &r.Content, &r.CreatedBy, &r.CreatedAt, &r.LastEdit, &r.LastEditBy, &r.IPAddress)
err := s.get.QueryRow(id).Scan(&r.ParentID, &r.Content, &r.CreatedBy, &r.CreatedAt, &r.LastEdit, &r.LastEditBy, &r.IP)
return &r, err
}

View File

@ -1,7 +1,7 @@
/*
*
* Reply Resources File
* Copyright Azareal 2016 - 2019
* Copyright Azareal 2016 - 2020
*
*/
package common
@ -37,7 +37,7 @@ type ReplyUser struct {
URLPrefix string
URLName string
Level int
//IPAddress string
//IP string
//Liked bool
//LikeCount int
//AttachCount int
@ -57,7 +57,7 @@ type Reply struct {
LastEdit int
LastEditBy int
ContentLines int
IPAddress string
IP string
Liked bool
LikeCount int
AttachCount int
@ -94,9 +94,9 @@ func init() {
// TODO: Write tests for this
// TODO: Wrap these queries in a transaction to make sure the state is consistent
func (reply *Reply) Like(uid int) (err error) {
func (r *Reply) Like(uid int) (err error) {
var rid int // unused, just here to avoid mutating reply.ID
err = replyStmts.isLiked.QueryRow(uid, reply.ID).Scan(&rid)
err = replyStmts.isLiked.QueryRow(uid, r.ID).Scan(&rid)
if err != nil && err != ErrNoRows {
return err
} else if err != ErrNoRows {
@ -104,66 +104,66 @@ func (reply *Reply) Like(uid int) (err error) {
}
score := 1
_, err = replyStmts.createLike.Exec(score, reply.ID, "replies", uid)
_, err = replyStmts.createLike.Exec(score, r.ID, "replies", uid)
if err != nil {
return err
}
_, err = replyStmts.addLikesToReply.Exec(1, reply.ID)
_, err = replyStmts.addLikesToReply.Exec(1, r.ID)
if err != nil {
return err
}
_, err = userStmts.incrementLiked.Exec(1, uid)
_ = Rstore.GetCache().Remove(reply.ID)
_ = Rstore.GetCache().Remove(r.ID)
return err
}
func (reply *Reply) Delete() error {
_, err := replyStmts.delete.Exec(reply.ID)
func (r *Reply) Delete() error {
_, err := replyStmts.delete.Exec(r.ID)
if err != nil {
return err
}
// TODO: Move this bit to *Topic
_, err = replyStmts.removeRepliesFromTopic.Exec(1, reply.ParentID)
_, err = replyStmts.removeRepliesFromTopic.Exec(1, r.ParentID)
tcache := Topics.GetCache()
if tcache != nil {
tcache.Remove(reply.ParentID)
tcache.Remove(r.ParentID)
}
_ = Rstore.GetCache().Remove(reply.ID)
_ = Rstore.GetCache().Remove(r.ID)
return err
}
func (reply *Reply) SetPost(content string) error {
topic, err := reply.Topic()
func (r *Reply) SetPost(content string) error {
topic, err := r.Topic()
if err != nil {
return err
}
content = PreparseMessage(html.UnescapeString(content))
parsedContent := ParseMessage(content, topic.ParentID, "forums")
_, err = replyStmts.edit.Exec(content, parsedContent, reply.ID) // TODO: Sniff if this changed anything to see if we hit an existing poll
_ = Rstore.GetCache().Remove(reply.ID)
_, err = replyStmts.edit.Exec(content, parsedContent, r.ID) // TODO: Sniff if this changed anything to see if we hit an existing poll
_ = Rstore.GetCache().Remove(r.ID)
return err
}
// TODO: Write tests for this
func (reply *Reply) SetPoll(pollID int) error {
_, err := replyStmts.setPoll.Exec(pollID, reply.ID) // TODO: Sniff if this changed anything to see if we hit a poll
_ = Rstore.GetCache().Remove(reply.ID)
func (r *Reply) SetPoll(pollID int) error {
_, err := replyStmts.setPoll.Exec(pollID, r.ID) // TODO: Sniff if this changed anything to see if we hit a poll
_ = Rstore.GetCache().Remove(r.ID)
return err
}
func (reply *Reply) Topic() (*Topic, error) {
return Topics.Get(reply.ParentID)
func (r *Reply) Topic() (*Topic, error) {
return Topics.Get(r.ParentID)
}
func (reply *Reply) GetID() int {
return reply.ID
func (r *Reply) GetID() int {
return r.ID
}
func (reply *Reply) GetTable() string {
func (r *Reply) GetTable() string {
return "replies"
}
// Copy gives you a non-pointer concurrency safe copy of the reply
func (reply *Reply) Copy() Reply {
return *reply
func (r *Reply) Copy() Reply {
return *r
}

View File

@ -42,7 +42,7 @@ func (s *SQLReplyStore) Get(id int) (*Reply, error) {
}
r = &Reply{ID: id}
err = s.get.QueryRow(id).Scan(&r.ParentID, &r.Content, &r.CreatedBy, &r.CreatedAt, &r.LastEdit, &r.LastEditBy, &r.IPAddress, &r.LikeCount, &r.AttachCount, &r.ActionType)
err = s.get.QueryRow(id).Scan(&r.ParentID, &r.Content, &r.CreatedBy, &r.CreatedAt, &r.LastEdit, &r.LastEditBy, &r.IP, &r.LikeCount, &r.AttachCount, &r.ActionType)
if err == nil {
_ = s.cache.Set(r)
}
@ -50,9 +50,9 @@ func (s *SQLReplyStore) Get(id int) (*Reply, error) {
}
// TODO: Write a test for this
func (s *SQLReplyStore) Create(topic *Topic, content string, ipaddress string, uid int) (id int, err error) {
func (s *SQLReplyStore) Create(t *Topic, content string, ip string, uid int) (rid int, err error) {
wcount := WordCount(content)
res, err := s.create.Exec(topic.ID, content, ParseMessage(content, topic.ParentID, "forums"), ipaddress, wcount, uid)
res, err := s.create.Exec(t.ID, content, ParseMessage(content, t.ParentID, "forums"), ip, wcount, uid)
if err != nil {
return 0, err
}
@ -61,7 +61,8 @@ func (s *SQLReplyStore) Create(topic *Topic, content string, ipaddress string, u
if err != nil {
return 0, err
}
return int(lastID), topic.AddReply(int(lastID), uid)
rid = int(lastID)
return rid, t.AddReply(rid, uid)
}
// TODO: Write a test for this

View File

@ -38,7 +38,7 @@ type Topic struct {
LastReplyID int
ParentID int
Status string // Deprecated. Marked for removal.
IPAddress string
IP string
ViewCount int64
PostCount int
LikeCount int
@ -64,7 +64,7 @@ type TopicUser struct {
LastReplyID int
ParentID int
Status string // Deprecated. Marked for removal.
IPAddress string
IP string
ViewCount int64
PostCount int
LikeCount int
@ -106,7 +106,7 @@ type TopicsRow struct {
LastReplyID int
ParentID int
Status string // Deprecated. Marked for removal. -Is there anything we could use it for?
IPAddress string
IP string
ViewCount int64
PostCount int
LikeCount int
@ -151,8 +151,8 @@ type WsTopicsRow struct {
}
// TODO: Can we get the client side to render the relative times instead?
func (row *TopicsRow) WebSockets() *WsTopicsRow {
return &WsTopicsRow{row.ID, row.Link, row.Title, row.CreatedBy, row.IsClosed, row.Sticky, row.CreatedAt, row.LastReplyAt, RelativeTime(row.LastReplyAt), row.LastReplyBy, row.LastReplyID, row.ParentID, row.ViewCount, row.PostCount, row.LikeCount, row.AttachCount, row.ClassName, row.Creator.WebSockets(), row.LastUser.WebSockets(), row.ForumName, row.ForumLink}
func (r *TopicsRow) WebSockets() *WsTopicsRow {
return &WsTopicsRow{r.ID, r.Link, r.Title, r.CreatedBy, r.IsClosed, r.Sticky, r.CreatedAt, r.LastReplyAt, RelativeTime(r.LastReplyAt), r.LastReplyBy, r.LastReplyID, r.ParentID, r.ViewCount, r.PostCount, r.LikeCount, r.AttachCount, r.ClassName, r.Creator.WebSockets(), r.LastUser.WebSockets(), r.ForumName, r.ForumLink}
}
// TODO: Stop relying on so many struct types?
@ -165,12 +165,12 @@ func (t *Topic) TopicsRow() *TopicsRow {
forumName := ""
forumLink := ""
return &TopicsRow{t.ID, t.Link, t.Title, t.Content, t.CreatedBy, t.IsClosed, t.Sticky, t.CreatedAt, t.LastReplyAt, t.LastReplyBy, t.LastReplyID, t.ParentID, t.Status, t.IPAddress, t.ViewCount, t.PostCount, t.LikeCount, t.AttachCount, lastPage, t.ClassName, t.Poll, t.Data, creator, "", contentLines, lastUser, forumName, forumLink, t.Rids}
return &TopicsRow{t.ID, t.Link, t.Title, t.Content, t.CreatedBy, t.IsClosed, t.Sticky, t.CreatedAt, t.LastReplyAt, t.LastReplyBy, t.LastReplyID, t.ParentID, t.Status, t.IP, t.ViewCount, t.PostCount, t.LikeCount, t.AttachCount, lastPage, t.ClassName, t.Poll, t.Data, creator, "", contentLines, lastUser, forumName, forumLink, t.Rids}
}
// ! Some data may be lost in the conversion
func (t *TopicsRow) Topic() *Topic {
return &Topic{t.ID, t.Link, t.Title, t.Content, t.CreatedBy, t.IsClosed, t.Sticky, t.CreatedAt, t.LastReplyAt, t.LastReplyBy, t.LastReplyID, t.ParentID, t.Status, t.IPAddress, t.ViewCount, t.PostCount, t.LikeCount, t.AttachCount, t.ClassName, t.Poll, t.Data, t.Rids}
return &Topic{t.ID, t.Link, t.Title, t.Content, t.CreatedBy, t.IsClosed, t.Sticky, t.CreatedAt, t.LastReplyAt, t.LastReplyBy, t.LastReplyID, t.ParentID, t.Status, t.IP, t.ViewCount, t.PostCount, t.LikeCount, t.AttachCount, t.ClassName, t.Poll, t.Data, t.Rids}
}
// ! Not quite safe as Topic doesn't contain all the data needed to constructs a WsTopicsRow
@ -239,99 +239,99 @@ func init() {
// Flush the topic out of the cache
// ? - We do a CacheRemove() here instead of mutating the pointer to avoid creating a race condition
func (topic *Topic) cacheRemove() {
func (t *Topic) cacheRemove() {
tcache := Topics.GetCache()
if tcache != nil {
tcache.Remove(topic.ID)
tcache.Remove(t.ID)
}
TopicListThaw.Thaw()
}
// TODO: Write a test for this
func (topic *Topic) AddReply(rid int, uid int) (err error) {
_, err = topicStmts.addReplies.Exec(1, uid, topic.ID)
func (t *Topic) AddReply(rid int, uid int) (err error) {
_, err = topicStmts.addReplies.Exec(1, uid, t.ID)
if err != nil {
return err
}
_, err = topicStmts.updateLastReply.Exec(rid, rid, topic.ID)
topic.cacheRemove()
_, err = topicStmts.updateLastReply.Exec(rid, rid, t.ID)
t.cacheRemove()
return err
}
func (topic *Topic) Lock() (err error) {
_, err = topicStmts.lock.Exec(topic.ID)
topic.cacheRemove()
func (t *Topic) Lock() (err error) {
_, err = topicStmts.lock.Exec(t.ID)
t.cacheRemove()
return err
}
func (topic *Topic) Unlock() (err error) {
_, err = topicStmts.unlock.Exec(topic.ID)
topic.cacheRemove()
func (t *Topic) Unlock() (err error) {
_, err = topicStmts.unlock.Exec(t.ID)
t.cacheRemove()
return err
}
func (topic *Topic) MoveTo(destForum int) (err error) {
_, err = topicStmts.moveTo.Exec(destForum, topic.ID)
topic.cacheRemove()
func (t *Topic) MoveTo(destForum int) (err error) {
_, err = topicStmts.moveTo.Exec(destForum, t.ID)
t.cacheRemove()
if err != nil {
return err
}
err = Attachments.MoveTo(destForum, topic.ID, "topics")
err = Attachments.MoveTo(destForum, t.ID, "topics")
if err != nil {
return err
}
return Attachments.MoveToByExtra(destForum, "replies", strconv.Itoa(topic.ID))
return Attachments.MoveToByExtra(destForum, "replies", strconv.Itoa(t.ID))
}
// TODO: We might want more consistent terminology rather than using stick in some places and pin in others. If you don't understand the difference, there is none, they are one and the same.
func (topic *Topic) Stick() (err error) {
_, err = topicStmts.stick.Exec(topic.ID)
topic.cacheRemove()
func (t *Topic) Stick() (err error) {
_, err = topicStmts.stick.Exec(t.ID)
t.cacheRemove()
return err
}
func (topic *Topic) Unstick() (err error) {
_, err = topicStmts.unstick.Exec(topic.ID)
topic.cacheRemove()
func (t *Topic) Unstick() (err error) {
_, err = topicStmts.unstick.Exec(t.ID)
t.cacheRemove()
return err
}
// TODO: Test this
// TODO: Use a transaction for this
func (topic *Topic) Like(score int, uid int) (err error) {
func (t *Topic) Like(score int, uid int) (err error) {
var disp int // Unused
err = topicStmts.hasLikedTopic.QueryRow(uid, topic.ID).Scan(&disp)
err = topicStmts.hasLikedTopic.QueryRow(uid, t.ID).Scan(&disp)
if err != nil && err != ErrNoRows {
return err
} else if err != ErrNoRows {
return ErrAlreadyLiked
}
_, err = topicStmts.createLike.Exec(score, topic.ID, "topics", uid)
_, err = topicStmts.createLike.Exec(score, t.ID, "topics", uid)
if err != nil {
return err
}
_, err = topicStmts.addLikesToTopic.Exec(1, topic.ID)
_, err = topicStmts.addLikesToTopic.Exec(1, t.ID)
if err != nil {
return err
}
_, err = userStmts.incrementLiked.Exec(1, uid)
topic.cacheRemove()
t.cacheRemove()
return err
}
// TODO: Implement this
func (topic *Topic) Unlike(uid int) error {
topic.cacheRemove()
func (t *Topic) Unlike(uid int) error {
t.cacheRemove()
return nil
}
// TODO: Use a transaction here
func (topic *Topic) Delete() error {
topicCreator, err := Users.Get(topic.CreatedBy)
func (t *Topic) Delete() error {
topicCreator, err := Users.Get(t.CreatedBy)
if err == nil {
wcount := WordCount(topic.Content)
wcount := WordCount(t.Content)
err = topicCreator.DecreasePostStats(wcount, true)
if err != nil {
return err
@ -340,26 +340,26 @@ func (topic *Topic) Delete() error {
return err
}
err = Forums.RemoveTopic(topic.ParentID)
err = Forums.RemoveTopic(t.ParentID)
if err != nil && err != ErrNoRows {
return err
}
_, err = topicStmts.delete.Exec(topic.ID)
topic.cacheRemove()
_, err = topicStmts.delete.Exec(t.ID)
t.cacheRemove()
if err != nil {
return err
}
_, err = topicStmts.deleteActivitySubs.Exec(topic.ID)
_, err = topicStmts.deleteActivitySubs.Exec(t.ID)
if err != nil {
return err
}
_, err = topicStmts.deleteActivity.Exec(topic.ID)
_, err = topicStmts.deleteActivity.Exec(t.ID)
return err
}
// TODO: Write tests for this
func (topic *Topic) Update(name string, content string) error {
func (t *Topic) Update(name string, content string) error {
name = SanitiseSingleLine(html.UnescapeString(name))
if name == "" {
return ErrNoTitle
@ -370,25 +370,25 @@ func (topic *Topic) Update(name string, content string) error {
}
content = PreparseMessage(html.UnescapeString(content))
parsedContent := ParseMessage(content, topic.ParentID, "forums")
_, err := topicStmts.edit.Exec(name, content, parsedContent, topic.ID)
topic.cacheRemove()
parsedContent := ParseMessage(content, t.ParentID, "forums")
_, err := topicStmts.edit.Exec(name, content, parsedContent, t.ID)
t.cacheRemove()
return err
}
func (topic *Topic) SetPoll(pollID int) error {
_, err := topicStmts.setPoll.Exec(pollID, topic.ID) // TODO: Sniff if this changed anything to see if we hit an existing poll
topic.cacheRemove()
func (t *Topic) SetPoll(pollID int) error {
_, err := topicStmts.setPoll.Exec(pollID, t.ID) // TODO: Sniff if this changed anything to see if we hit an existing poll
t.cacheRemove()
return err
}
// TODO: Have this go through the ReplyStore?
func (topic *Topic) CreateActionReply(action string, ipaddress string, uid int) (err error) {
res, err := topicStmts.createAction.Exec(topic.ID, action, ipaddress, uid)
func (t *Topic) CreateActionReply(action string, ip string, uid int) (err error) {
res, err := topicStmts.createAction.Exec(t.ID, action, ip, uid)
if err != nil {
return err
}
_, err = topicStmts.addReplies.Exec(1, uid, topic.ID)
_, err = topicStmts.addReplies.Exec(1, uid, t.ID)
if err != nil {
return err
}
@ -397,8 +397,8 @@ func (topic *Topic) CreateActionReply(action string, ipaddress string, uid int)
return err
}
rid := int(lid)
_, err = topicStmts.updateLastReply.Exec(rid, rid, topic.ID)
topic.cacheRemove()
_, err = topicStmts.updateLastReply.Exec(rid, rid, t.ID)
t.cacheRemove()
// ? - Update the last topic cache for the parent forum?
return err
}
@ -487,23 +487,23 @@ func (ru *ReplyUser) Init() error {
}
// TODO: Factor TopicUser into a *Topic and *User, as this starting to become overly complicated x.x
func (topic *TopicUser) Replies(offset int, pFrag int, user *User) (rlist []*ReplyUser, ogdesc string, err error) {
func (t *TopicUser) Replies(offset int, pFrag int, user *User) (rlist []*ReplyUser, ogdesc string, err error) {
var likedMap map[int]int
if user.Liked > 0 {
likedMap = make(map[int]int)
}
var likedQueryList = []int{user.ID}
likedQueryList := []int{user.ID}
var attachMap map[int]int
if user.Perms.EditReply {
attachMap = make(map[int]int)
}
var attachQueryList = []int{}
attachQueryList := []int{}
var rid int
if len(topic.Rids) > 0 {
if len(t.Rids) > 0 {
//log.Print("have rid")
rid = topic.Rids[0]
rid = t.Rids[0]
}
re, err := Rstore.GetCache().Get(rid)
ucache := Users.GetCache()
@ -524,7 +524,7 @@ func (topic *TopicUser) Replies(offset int, pFrag int, user *User) (rlist []*Rep
if err != nil {
return nil, "", err
}
reply.ContentHtml = ParseMessage(reply.Content, topic.ParentID, "forums")
reply.ContentHtml = ParseMessage(reply.Content, t.ParentID, "forums")
// TODO: Do this more efficiently by avoiding the allocations entirely in ParseMessage, if there's nothing to do.
if reply.ContentHtml == reply.Content {
reply.ContentHtml = reply.Content
@ -549,7 +549,7 @@ func (topic *TopicUser) Replies(offset int, pFrag int, user *User) (rlist []*Rep
hTbl.VhookNoRet("topic_reply_row_assign", &rlist, &reply)
rlist = append(rlist, reply)
} else {
rows, err := topicStmts.getReplies.Query(topic.ID, offset, Config.ItemsPerPage)
rows, err := topicStmts.getReplies.Query(t.ID, offset, Config.ItemsPerPage)
if err != nil {
return nil, "", err
}
@ -557,7 +557,7 @@ func (topic *TopicUser) Replies(offset int, pFrag int, user *User) (rlist []*Rep
for rows.Next() {
reply = &ReplyUser{}
err := rows.Scan(&reply.ID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.Avatar, &reply.CreatedByName, &reply.Group, &reply.URLPrefix, &reply.URLName, &reply.Level, &reply.IPAddress, &reply.LikeCount, &reply.AttachCount, &reply.ActionType)
err := rows.Scan(&reply.ID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.Avatar, &reply.CreatedByName, &reply.Group, &reply.URLPrefix, &reply.URLName, &reply.Level, &reply.IP, &reply.LikeCount, &reply.AttachCount, &reply.ActionType)
if err != nil {
return nil, "", err
}
@ -566,7 +566,7 @@ func (topic *TopicUser) Replies(offset int, pFrag int, user *User) (rlist []*Rep
if err != nil {
return nil, "", err
}
reply.ContentHtml = ParseMessage(reply.Content, topic.ParentID, "forums")
reply.ContentHtml = ParseMessage(reply.Content, t.ParentID, "forums")
if reply.ID == pFrag {
ogdesc = reply.Content
@ -627,28 +627,28 @@ func (topic *TopicUser) Replies(offset int, pFrag int, user *User) (rlist []*Rep
}
// TODO: Test this
func (topic *Topic) Author() (*User, error) {
return Users.Get(topic.CreatedBy)
func (t *Topic) Author() (*User, error) {
return Users.Get(t.CreatedBy)
}
func (topic *Topic) GetID() int {
return topic.ID
func (t *Topic) GetID() int {
return t.ID
}
func (topic *Topic) GetTable() string {
func (t *Topic) GetTable() string {
return "topics"
}
// Copy gives you a non-pointer concurrency safe copy of the topic
func (topic *Topic) Copy() Topic {
return *topic
func (t *Topic) Copy() Topic {
return *t
}
// TODO: Load LastReplyAt and LastReplyID?
func TopicByReplyID(rid int) (*Topic, error) {
topic := Topic{ID: 0}
err := topicStmts.getByReplyID.QueryRow(rid).Scan(&topic.ID, &topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.ViewCount, &topic.PostCount, &topic.LikeCount, &topic.Poll, &topic.Data)
topic.Link = BuildTopicURL(NameToSlug(topic.Title), topic.ID)
return &topic, err
t := Topic{ID: 0}
err := topicStmts.getByReplyID.QueryRow(rid).Scan(&t.ID, &t.Title, &t.Content, &t.CreatedBy, &t.CreatedAt, &t.IsClosed, &t.Sticky, &t.ParentID, &t.IP, &t.ViewCount, &t.PostCount, &t.LikeCount, &t.Poll, &t.Data)
t.Link = BuildTopicURL(NameToSlug(t.Title), t.ID)
return &t, err
}
// TODO: Refactor the caller to take a Topic and a User rather than a combined TopicUser
@ -684,49 +684,49 @@ func GetTopicUser(user *User, tid int) (tu TopicUser, err error) {
tu = TopicUser{ID: tid}
// TODO: This misses some important bits...
err = topicStmts.getTopicUser.QueryRow(tid).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.LastReplyAt, &tu.LastReplyBy, &tu.LastReplyID, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IPAddress, &tu.ViewCount, &tu.PostCount, &tu.LikeCount, &tu.AttachCount, &tu.Poll, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
err = topicStmts.getTopicUser.QueryRow(tid).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.LastReplyAt, &tu.LastReplyBy, &tu.LastReplyID, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IP, &tu.ViewCount, &tu.PostCount, &tu.LikeCount, &tu.AttachCount, &tu.Poll, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
tu.Avatar, tu.MicroAvatar = BuildAvatar(tu.CreatedBy, tu.Avatar)
tu.Link = BuildTopicURL(NameToSlug(tu.Title), tu.ID)
tu.UserLink = BuildProfileURL(NameToSlug(tu.CreatedByName), tu.CreatedBy)
tu.Tag = Groups.DirtyGet(tu.Group).Tag
if tcache != nil {
theTopic := Topic{ID: tu.ID, Link: tu.Link, Title: tu.Title, Content: tu.Content, CreatedBy: tu.CreatedBy, IsClosed: tu.IsClosed, Sticky: tu.Sticky, CreatedAt: tu.CreatedAt, LastReplyAt: tu.LastReplyAt, LastReplyID: tu.LastReplyID, ParentID: tu.ParentID, IPAddress: tu.IPAddress, ViewCount: tu.ViewCount, PostCount: tu.PostCount, LikeCount: tu.LikeCount, AttachCount: tu.AttachCount, Poll: tu.Poll}
theTopic := Topic{ID: tu.ID, Link: tu.Link, Title: tu.Title, Content: tu.Content, CreatedBy: tu.CreatedBy, IsClosed: tu.IsClosed, Sticky: tu.Sticky, CreatedAt: tu.CreatedAt, LastReplyAt: tu.LastReplyAt, LastReplyID: tu.LastReplyID, ParentID: tu.ParentID, IP: tu.IP, ViewCount: tu.ViewCount, PostCount: tu.PostCount, LikeCount: tu.LikeCount, AttachCount: tu.AttachCount, Poll: tu.Poll}
//log.Printf("theTopic: %+v\n", theTopic)
_ = tcache.Set(&theTopic)
}
return tu, err
}
func copyTopicToTopicUser(topic *Topic, user *User) (tu TopicUser) {
tu.UserLink = user.Link
tu.CreatedByName = user.Name
tu.Group = user.Group
tu.Avatar = user.Avatar
tu.MicroAvatar = user.MicroAvatar
tu.URLPrefix = user.URLPrefix
tu.URLName = user.URLName
tu.Level = user.Level
func copyTopicToTopicUser(t *Topic, u *User) (tu TopicUser) {
tu.UserLink = u.Link
tu.CreatedByName = u.Name
tu.Group = u.Group
tu.Avatar = u.Avatar
tu.MicroAvatar = u.MicroAvatar
tu.URLPrefix = u.URLPrefix
tu.URLName = u.URLName
tu.Level = u.Level
tu.ID = topic.ID
tu.Link = topic.Link
tu.Title = topic.Title
tu.Content = topic.Content
tu.CreatedBy = topic.CreatedBy
tu.IsClosed = topic.IsClosed
tu.Sticky = topic.Sticky
tu.CreatedAt = topic.CreatedAt
tu.LastReplyAt = topic.LastReplyAt
tu.LastReplyBy = topic.LastReplyBy
tu.ParentID = topic.ParentID
tu.IPAddress = topic.IPAddress
tu.ViewCount = topic.ViewCount
tu.PostCount = topic.PostCount
tu.LikeCount = topic.LikeCount
tu.AttachCount = topic.AttachCount
tu.Poll = topic.Poll
tu.Data = topic.Data
tu.Rids = topic.Rids
tu.ID = t.ID
tu.Link = t.Link
tu.Title = t.Title
tu.Content = t.Content
tu.CreatedBy = t.CreatedBy
tu.IsClosed = t.IsClosed
tu.Sticky = t.Sticky
tu.CreatedAt = t.CreatedAt
tu.LastReplyAt = t.LastReplyAt
tu.LastReplyBy = t.LastReplyBy
tu.ParentID = t.ParentID
tu.IP = t.IP
tu.ViewCount = t.ViewCount
tu.PostCount = t.PostCount
tu.LikeCount = t.LikeCount
tu.AttachCount = t.AttachCount
tu.Poll = t.Poll
tu.Data = t.Data
tu.Rids = t.Rids
return tu
}

View File

@ -94,7 +94,7 @@ func (s *DefaultTopicStore) Get(id int) (topic *Topic, err error) {
// BypassGet will always bypass the cache and pull the topic directly from the database
func (s *DefaultTopicStore) BypassGet(id int) (*Topic, error) {
t := &Topic{ID: id}
err := s.get.QueryRow(id).Scan(&t.Title, &t.Content, &t.CreatedBy, &t.CreatedAt, &t.LastReplyBy, &t.LastReplyAt, &t.LastReplyID, &t.IsClosed, &t.Sticky, &t.ParentID, &t.IPAddress, &t.ViewCount, &t.PostCount, &t.LikeCount, &t.AttachCount, &t.Poll, &t.Data)
err := s.get.QueryRow(id).Scan(&t.Title, &t.Content, &t.CreatedBy, &t.CreatedAt, &t.LastReplyBy, &t.LastReplyAt, &t.LastReplyID, &t.IsClosed, &t.Sticky, &t.ParentID, &t.IP, &t.ViewCount, &t.PostCount, &t.LikeCount, &t.AttachCount, &t.Poll, &t.Data)
if err == nil {
t.Link = BuildTopicURL(NameToSlug(t.Title), id)
}
@ -151,7 +151,7 @@ func (s *DefaultTopicStore) BulkGetMap(ids []int) (list map[int]*Topic, err erro
for rows.Next() {
t := &Topic{}
err := rows.Scan(&t.ID, &t.Title, &t.Content, &t.CreatedBy, &t.CreatedAt, &t.LastReplyBy, &t.LastReplyAt, &t.LastReplyID, &t.IsClosed, &t.Sticky, &t.ParentID, &t.IPAddress, &t.ViewCount, &t.PostCount, &t.LikeCount, &t.AttachCount, &t.Poll, &t.Data)
err := rows.Scan(&t.ID, &t.Title, &t.Content, &t.CreatedBy, &t.CreatedAt, &t.LastReplyBy, &t.LastReplyAt, &t.LastReplyID, &t.IsClosed, &t.Sticky, &t.ParentID, &t.IP, &t.ViewCount, &t.PostCount, &t.LikeCount, &t.AttachCount, &t.Poll, &t.Data)
if err != nil {
return list, err
}

View File

@ -40,11 +40,11 @@ func NewMemoryUserCache(capacity int) *MemoryUserCache {
}
// TODO: Avoid deallocating topic list users
func (mus *MemoryUserCache) DeallocOverflow(evictPriority bool) (evicted int) {
var toEvict = make([]int, 10)
var evIndex = 0
mus.RLock()
for _, user := range mus.items {
func (s *MemoryUserCache) DeallocOverflow(evictPriority bool) (evicted int) {
toEvict := make([]int, 10)
evIndex := 0
s.RLock()
for _, user := range s.items {
if /*user.LastActiveAt < lastActiveCutoff && */ user.Score == 0 && !user.IsMod {
if EnableWebsockets && WsHub.HasUser(user.ID) {
continue
@ -56,13 +56,13 @@ func (mus *MemoryUserCache) DeallocOverflow(evictPriority bool) (evicted int) {
}
}
}
mus.RUnlock()
s.RUnlock()
// Clear some of the less active users now with a bit more aggressiveness
if evIndex == 0 && evictPriority {
toEvict = make([]int, 20)
mus.RLock()
for _, user := range mus.items {
s.RLock()
for _, user := range s.items {
if user.Score < 100 && !user.IsMod {
if EnableWebsockets && WsHub.HasUser(user.ID) {
continue
@ -74,11 +74,11 @@ func (mus *MemoryUserCache) DeallocOverflow(evictPriority bool) (evicted int) {
}
}
}
mus.RUnlock()
s.RUnlock()
}
// Remove zero IDs from the evictable list, so we don't waste precious cycles locked for those
var lastZero = -1
lastZero := -1
for i, uid := range toEvict {
if uid == 0 {
lastZero = i
@ -88,15 +88,15 @@ func (mus *MemoryUserCache) DeallocOverflow(evictPriority bool) (evicted int) {
toEvict = toEvict[:lastZero]
}
mus.BulkRemove(toEvict)
s.BulkRemove(toEvict)
return len(toEvict)
}
// Get fetches a user by ID. Returns ErrNoRows if not present.
func (mus *MemoryUserCache) Get(id int) (*User, error) {
mus.RLock()
item, ok := mus.items[id]
mus.RUnlock()
func (s *MemoryUserCache) Get(id int) (*User, error) {
s.RLock()
item, ok := s.items[id]
s.RUnlock()
if ok {
return item, nil
}
@ -104,19 +104,19 @@ func (mus *MemoryUserCache) Get(id int) (*User, error) {
}
// BulkGet fetches multiple users by their IDs. Indices without users will be set to nil, so make sure you check for those, we might want to change this behaviour to make it less confusing.
func (mus *MemoryUserCache) BulkGet(ids []int) (list []*User) {
func (s *MemoryUserCache) BulkGet(ids []int) (list []*User) {
list = make([]*User, len(ids))
mus.RLock()
s.RLock()
for i, id := range ids {
list[i] = mus.items[id]
list[i] = s.items[id]
}
mus.RUnlock()
s.RUnlock()
return list
}
// GetUnsafe fetches a user by ID. Returns ErrNoRows if not present. THIS METHOD IS NOT THREAD-SAFE.
func (mus *MemoryUserCache) GetUnsafe(id int) (*User, error) {
item, ok := mus.items[id]
func (s *MemoryUserCache) GetUnsafe(id int) (*User, error) {
item, ok := s.items[id]
if ok {
return item, nil
}
@ -124,107 +124,107 @@ func (mus *MemoryUserCache) GetUnsafe(id int) (*User, error) {
}
// Set overwrites the value of a user in the cache, whether it's present or not. May return a capacity overflow error.
func (mus *MemoryUserCache) Set(item *User) error {
mus.Lock()
user, ok := mus.items[item.ID]
func (s *MemoryUserCache) Set(item *User) error {
s.Lock()
user, ok := s.items[item.ID]
if ok {
mus.Unlock()
s.Unlock()
*user = *item
} else if int(mus.length) >= mus.capacity {
mus.Unlock()
} else if int(s.length) >= s.capacity {
s.Unlock()
return ErrStoreCapacityOverflow
} else {
mus.items[item.ID] = item
mus.Unlock()
atomic.AddInt64(&mus.length, 1)
s.items[item.ID] = item
s.Unlock()
atomic.AddInt64(&s.length, 1)
}
return nil
}
// Add adds a user to the cache, similar to Set, but it's only intended for new items. This method might be deprecated in the near future, use Set. May return a capacity overflow error.
// ? Is this redundant if we have Set? Are the efficiency wins worth this? Is this even used?
func (mus *MemoryUserCache) Add(item *User) error {
mus.Lock()
if int(mus.length) >= mus.capacity {
mus.Unlock()
func (s *MemoryUserCache) Add(item *User) error {
s.Lock()
if int(s.length) >= s.capacity {
s.Unlock()
return ErrStoreCapacityOverflow
}
mus.items[item.ID] = item
mus.length = int64(len(mus.items))
mus.Unlock()
s.items[item.ID] = item
s.length = int64(len(s.items))
s.Unlock()
return nil
}
// AddUnsafe is the unsafe version of Add. May return a capacity overflow error. THIS METHOD IS NOT THREAD-SAFE.
func (mus *MemoryUserCache) AddUnsafe(item *User) error {
if int(mus.length) >= mus.capacity {
func (s *MemoryUserCache) AddUnsafe(item *User) error {
if int(s.length) >= s.capacity {
return ErrStoreCapacityOverflow
}
mus.items[item.ID] = item
mus.length = int64(len(mus.items))
s.items[item.ID] = item
s.length = int64(len(s.items))
return nil
}
// Remove removes a user from the cache by ID, if they exist. Returns ErrNoRows if no items exist.
func (mus *MemoryUserCache) Remove(id int) error {
mus.Lock()
_, ok := mus.items[id]
func (s *MemoryUserCache) Remove(id int) error {
s.Lock()
_, ok := s.items[id]
if !ok {
mus.Unlock()
s.Unlock()
return ErrNoRows
}
delete(mus.items, id)
mus.Unlock()
atomic.AddInt64(&mus.length, -1)
delete(s.items, id)
s.Unlock()
atomic.AddInt64(&s.length, -1)
return nil
}
// RemoveUnsafe is the unsafe version of Remove. THIS METHOD IS NOT THREAD-SAFE.
func (mus *MemoryUserCache) RemoveUnsafe(id int) error {
_, ok := mus.items[id]
func (s *MemoryUserCache) RemoveUnsafe(id int) error {
_, ok := s.items[id]
if !ok {
return ErrNoRows
}
delete(mus.items, id)
atomic.AddInt64(&mus.length, -1)
delete(s.items, id)
atomic.AddInt64(&s.length, -1)
return nil
}
func (mus *MemoryUserCache) BulkRemove(ids []int) {
func (s *MemoryUserCache) BulkRemove(ids []int) {
var rCount int64
mus.Lock()
s.Lock()
for _, id := range ids {
_, ok := mus.items[id]
_, ok := s.items[id]
if ok {
delete(mus.items, id)
delete(s.items, id)
rCount++
}
}
mus.Unlock()
atomic.AddInt64(&mus.length, -rCount)
s.Unlock()
atomic.AddInt64(&s.length, -rCount)
}
// Flush removes all the users from the cache, useful for tests.
func (mus *MemoryUserCache) Flush() {
mus.Lock()
mus.items = make(map[int]*User)
mus.length = 0
mus.Unlock()
func (s *MemoryUserCache) Flush() {
s.Lock()
s.items = make(map[int]*User)
s.length = 0
s.Unlock()
}
// ! Is this concurrent?
// Length returns the number of users in the memory cache
func (mus *MemoryUserCache) Length() int {
return int(mus.length)
func (s *MemoryUserCache) Length() int {
return int(s.length)
}
// SetCapacity sets the maximum number of users which this cache can hold
func (mus *MemoryUserCache) SetCapacity(capacity int) {
func (s *MemoryUserCache) SetCapacity(capacity int) {
// Ints are moved in a single instruction, so this should be thread-safe
mus.capacity = capacity
s.capacity = capacity
}
// GetCapacity returns the maximum number of users this cache can hold
func (mus *MemoryUserCache) GetCapacity() int {
return mus.capacity
func (s *MemoryUserCache) GetCapacity() int {
return s.capacity
}

View File

@ -793,7 +793,7 @@ func BenchmarkQueryTopicParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
var tu c.TopicUser
for pb.Next() {
err := db.QueryRow("select topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.views, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.url_prefix, users.url_name, users.level from topics left join users ON topics.createdBy = users.uid where tid = ?", 1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IPAddress, &tu.ViewCount, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
err := db.QueryRow("select topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.views, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.url_prefix, users.url_name, users.level from topics left join users ON topics.createdBy = users.uid where tid = ?", 1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IP, &tu.ViewCount, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
if err == ErrNoRows {
log.Fatal("No rows found!")
return
@ -822,7 +822,7 @@ func BenchmarkQueryPreparedTopicParallel(b *testing.B) {
defer getTopicUser.Close()
for pb.Next() {
err := getTopicUser.QueryRow(1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IPAddress, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
err := getTopicUser.QueryRow(1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IP, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
if err == ErrNoRows {
b.Fatal("No rows found!")
return
@ -878,7 +878,7 @@ func BenchmarkQueriesSerial(b *testing.B) {
var tu c.TopicUser
b.Run("topic", func(b *testing.B) {
for i := 0; i < b.N; i++ {
err := db.QueryRow("select topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.url_prefix, users.url_name, users.level from topics left join users ON topics.createdBy = users.uid where tid = ?", 1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IPAddress, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
err := db.QueryRow("select topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.url_prefix, users.url_name, users.level from topics left join users ON topics.createdBy = users.uid where tid = ?", 1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IP, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
if err == ErrNoRows {
b.Fatal("No rows found!")
return
@ -907,7 +907,7 @@ func BenchmarkQueriesSerial(b *testing.B) {
}
})
var replyItem c.ReplyUser
var r c.ReplyUser
var isSuperAdmin bool
var group int
b.Run("topic_replies_scan", func(b *testing.B) {
@ -918,7 +918,7 @@ func BenchmarkQueriesSerial(b *testing.B) {
return
}
for rows.Next() {
err := rows.Scan(&replyItem.ID, &replyItem.Content, &replyItem.CreatedBy, &replyItem.CreatedAt, &replyItem.LastEdit, &replyItem.LastEditBy, &replyItem.Avatar, &replyItem.CreatedByName, &isSuperAdmin, &group, &replyItem.URLPrefix, &replyItem.URLName, &replyItem.Level, &replyItem.IPAddress)
err := rows.Scan(&r.ID, &r.Content, &r.CreatedBy, &r.CreatedAt, &r.LastEdit, &r.LastEditBy, &r.Avatar, &r.CreatedByName, &isSuperAdmin, &group, &r.URLPrefix, &r.URLName, &r.Level, &r.IP)
if err != nil {
b.Fatal(err)
return

View File

@ -497,7 +497,7 @@ func topicStoreTest(t *testing.T, newID int) {
return ""
}
var testTopic = func(tid int, title string, content string, createdBy int, ip string, parentID int, isClosed bool, sticky bool) {
testTopic := func(tid int, title string, content string, createdBy int, ip string, parentID int, isClosed bool, sticky bool) {
topic, err = c.Topics.Get(tid)
recordMustExist(t, err, fmt.Sprintf("Couldn't find TID #%d", tid))
expect(t, topic.ID == tid, fmt.Sprintf("topic.ID does not match the requested TID. Got '%d' instead.", topic.ID))
@ -505,7 +505,7 @@ func topicStoreTest(t *testing.T, newID int) {
expect(t, topic.Title == title, fmt.Sprintf("The topic's name should be '%s', not %s", title, topic.Title))
expect(t, topic.Content == content, fmt.Sprintf("The topic's body should be '%s', not %s", content, topic.Content))
expect(t, topic.CreatedBy == createdBy, fmt.Sprintf("The topic's creator should be %d, not %d", createdBy, topic.CreatedBy))
expect(t, topic.IPAddress == ip, fmt.Sprintf("The topic's IP Address should be '%s', not %s", ip, topic.IPAddress))
expect(t, topic.IP == ip, fmt.Sprintf("The topic's IP should be '%s', not %s", ip, topic.IP))
expect(t, topic.ParentID == parentID, fmt.Sprintf("The topic's parent forum should be %d, not %d", parentID, topic.ParentID))
expect(t, topic.IsClosed == isClosed, fmt.Sprintf("This topic should%s be locked", iFrag(topic.IsClosed)))
expect(t, topic.Sticky == sticky, fmt.Sprintf("This topic should%s be sticky", iFrag(topic.Sticky)))
@ -933,16 +933,16 @@ func TestReplyStore(t *testing.T) {
_, err = c.Rstore.Get(0)
recordMustNotExist(t, err, "RID #0 shouldn't exist")
var replyTest2 = func(reply *c.Reply, err error, rid int, parentID int, createdBy int, content string, ip string) {
replyTest2 := func(reply *c.Reply, err error, rid int, parentID int, createdBy int, content string, ip string) {
expectNilErr(t, err)
expect(t, reply.ID == rid, fmt.Sprintf("RID #%d has the wrong ID. It should be %d not %d", rid, rid, reply.ID))
expect(t, reply.ParentID == parentID, fmt.Sprintf("The parent topic of RID #%d should be %d not %d", rid, parentID, reply.ParentID))
expect(t, reply.CreatedBy == createdBy, fmt.Sprintf("The creator of RID #%d should be %d not %d", rid, createdBy, reply.CreatedBy))
expect(t, reply.Content == content, fmt.Sprintf("The contents of RID #%d should be '%s' not %s", rid, content, reply.Content))
expect(t, reply.IPAddress == ip, fmt.Sprintf("The IPAddress of RID#%d should be '%s' not %s", rid, ip, reply.IPAddress))
expect(t, reply.IP == ip, fmt.Sprintf("The IP of RID#%d should be '%s' not %s", rid, ip, reply.IP))
}
var replyTest = func(rid int, parentID int, createdBy int, content string, ip string) {
replyTest := func(rid int, parentID int, createdBy int, content string, ip string) {
reply, err := c.Rstore.Get(rid)
replyTest2(reply, err, rid, parentID, createdBy, content, ip)
reply, err = c.Rstore.GetCache().Get(rid)
@ -1043,7 +1043,7 @@ func TestProfileReplyStore(t *testing.T) {
expect(t, profileReply.ParentID == 1, fmt.Sprintf("The parent ID of the profile reply should be 1 not %d", profileReply.ParentID))
expect(t, profileReply.Content == "Haha", fmt.Sprintf("The profile reply's contents should be 'Haha' not '%s'", profileReply.Content))
expect(t, profileReply.CreatedBy == 1, fmt.Sprintf("The profile reply's creator should be 1 not %d", profileReply.CreatedBy))
expect(t, profileReply.IPAddress == "::1", fmt.Sprintf("The profile reply's IP Address should be '::1' not '%s'", profileReply.IPAddress))
expect(t, profileReply.IP == "::1", fmt.Sprintf("The profile reply's IP should be '::1' not '%s'", profileReply.IP))
err = profileReply.Delete()
expectNilErr(t, err)
@ -1104,7 +1104,7 @@ func TestLogs(t *testing.T) {
expect(t, log.Action == "something", "log.Action is not something")
expect(t, log.ElementID == 0, "log.ElementID is not 0")
expect(t, log.ElementType == "bumblefly", "log.ElementType is not bumblefly")
expect(t, log.IPAddress == "::1", "log.IPAddress is not ::1")
expect(t, log.IP == "::1", "log.IP is not ::1")
expect(t, log.ActorID == 1, "log.ActorID is not 1")
// TODO: Add a test for log.DoneAt? Maybe throw in some dates and times which are clearly impossible but which may occur due to timezone bugs?
}

View File

@ -39,19 +39,17 @@ func AccountLoginSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.R
username := c.SanitiseSingleLine(r.PostFormValue("username"))
uid, err, requiresExtraAuth := c.Auth.Authenticate(username, r.PostFormValue("password"))
if err != nil {
{
// TODO: uid is currently set to 0 as authenticate fetches the user by username and password. Get the actual uid, so we can alert the user of attempted logins? What if someone takes advantage of the response times to deduce if an account exists?
logItem := &c.LoginLogItem{UID: uid, Success: false, IPAddress: user.LastIP}
_, err := logItem.Create()
if err != nil {
return c.InternalError(err, w, r)
}
// TODO: uid is currently set to 0 as authenticate fetches the user by username and password. Get the actual uid, so we can alert the user of attempted logins? What if someone takes advantage of the response times to deduce if an account exists?
logItem := &c.LoginLogItem{UID: uid, Success: false, IP: user.LastIP}
_, err := logItem.Create()
if err != nil {
return c.InternalError(err, w, r)
}
return c.LocalError(err.Error(), w, r, user)
}
// TODO: Take 2FA into account
logItem := &c.LoginLogItem{UID: uid, Success: true, IPAddress: user.LastIP}
logItem := &c.LoginLogItem{UID: uid, Success: true, IP: user.LastIP}
_, err = logItem.Create()
if err != nil {
return c.InternalError(err, w, r)
@ -263,7 +261,7 @@ func AccountRegisterSubmit(w http.ResponseWriter, r *http.Request, user c.User)
}
}
regLog := c.RegLogItem{Username: username, Email: email, FailureReason: regErrReason, Success: regSuccess, IPAddress: user.LastIP}
regLog := c.RegLogItem{Username: username, Email: email, FailureReason: regErrReason, Success: regSuccess, IP: user.LastIP}
_, err = regLog.Create()
if err != nil {
return c.InternalError(err, w, r)

View File

@ -116,11 +116,11 @@ func LogsMod(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
if err != nil {
return c.InternalError(err, w, r)
}
var llist = make([]c.PageLogItem, len(logs))
llist := make([]c.PageLogItem, len(logs))
for index, log := range logs {
actor := handleUnknownUser(c.Users.Get(log.ActorID))
action := modlogsElementType(log.Action, log.ElementType, log.ElementID, actor)
llist[index] = c.PageLogItem{Action: template.HTML(action), IPAddress: log.IPAddress, DoneAt: log.DoneAt}
llist[index] = c.PageLogItem{Action: template.HTML(action), IP: log.IP, DoneAt: log.DoneAt}
}
pageList := c.Paginate(page, lastPage, 5)
@ -143,11 +143,11 @@ func LogsAdmin(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError
if err != nil {
return c.InternalError(err, w, r)
}
var llist = make([]c.PageLogItem, len(logs))
llist := make([]c.PageLogItem, len(logs))
for index, log := range logs {
actor := handleUnknownUser(c.Users.Get(log.ActorID))
action := modlogsElementType(log.Action, log.ElementType, log.ElementID, actor)
llist[index] = c.PageLogItem{Action: template.HTML(action), IPAddress: log.IPAddress, DoneAt: log.DoneAt}
llist[index] = c.PageLogItem{Action: template.HTML(action), IP: log.IP, DoneAt: log.DoneAt}
}
pageList := c.Paginate(page, lastPage, 5)

View File

@ -7,7 +7,7 @@
<div class="rowitem{{if not .Success}} bg_red{{end}}">
<span class="to_left">
<span>{{if .Success}}{{lang "account_logins_success"}}{{else}}{{lang "account_logins_failure"}}"{{end}}</span><br />
<small title="{{.IPAddress}}">{{.IPAddress}}</small>
<small title="{{.IP}}">{{.IP}}</small>
</span>
<span class="to_right">
<span title="{{.DoneAt}}">{{.DoneAt}}</span>

View File

@ -6,7 +6,7 @@
<div class="rowitem panel_compactrow">
<span class="to_left">
<span>{{.Action}}</span>
{{if $.CurrentUser.Perms.ViewIPs}}<br /><small title="{{.IPAddress}}">{{.IPAddress}}</small>{{end}}
{{if $.CurrentUser.Perms.ViewIPs}}<br /><small title="{{.IP}}">{{.IP}}</small>{{end}}
</span>
<span class="to_right">
<span title="{{.DoneAt}}">{{.DoneAt}}</span>

View File

@ -6,7 +6,7 @@
<div class="rowitem panel_compactrow">
<span class="to_left">
<span>{{.Action}}</span>
{{if $.CurrentUser.Perms.ViewIPs}}<br /><small title="{{.IPAddress}}">{{.IPAddress}}</small>{{end}}
{{if $.CurrentUser.Perms.ViewIPs}}<br /><small title="{{.IP}}">{{.IP}}</small>{{end}}
</span>
<span class="to_right">
<span title="{{.DoneAt}}">{{.DoneAt}}</span>

View File

@ -6,7 +6,7 @@
<div class="rowitem panel_compactrow{{if not .Success}} bg_red{{end}}">
<span class="to_left{{if not .Success}} panel_registration_attempt{{end}}">
<span>{{if not .Success}}{{lang "panel_logs_registration_attempt"}}: {{end}}{{.Username}} ({{lang "panel_logs_registration_email"}}: {{.Email}}){{if .ParsedReason}} ({{lang "panel_logs_registration_reason"}}: {{.ParsedReason}}){{end}}</span>
{{if $.CurrentUser.Perms.ViewIPs}}<br /><small title="{{.IPAddress}}">{{.IPAddress}}</small>{{end}}
{{if $.CurrentUser.Perms.ViewIPs}}<br /><small title="{{.IP}}">{{.IP}}</small>{{end}}
</span>
<span class="to_right">
<span title="{{.DoneAt}}">{{.DoneAt}}</span>

View File

@ -54,7 +54,7 @@
{{if .CurrentUser.Perms.CloseTopic}}{{if .Topic.IsClosed}}<a class="mod_button" href='/topic/unlock/submit/{{.Topic.ID}}?s={{.CurrentUser.Session}}' title="{{lang "topic.unlock_tooltip"}}" aria-label="{{lang "topic.unlock_aria"}}"><button class="username unlock_label"></button></a>{{else}}<a href='/topic/lock/submit/{{.Topic.ID}}?s={{.CurrentUser.Session}}' class="mod_button" title="{{lang "topic.lock_tooltip"}}" aria-label="{{lang "topic.lock_aria"}}"><button class="username lock_label"></button></a>{{end}}{{end}}
{{if .CurrentUser.Perms.PinTopic}}{{if .Topic.Sticky}}<a class="mod_button" href='/topic/unstick/submit/{{.Topic.ID}}?s={{.CurrentUser.Session}}' title="{{lang "topic.unpin_tooltip"}}" aria-label="{{lang "topic.unpin_aria"}}"><button class="username unpin_label"></button></a>{{else}}<a href='/topic/stick/submit/{{.Topic.ID}}?s={{.CurrentUser.Session}}' class="mod_button" title="{{lang "topic.pin_tooltip"}}" aria-label="{{lang "topic.pin_aria"}}"><button class="username pin_label"></button></a>{{end}}{{end}}
{{if .CurrentUser.Perms.ViewIPs}}<a class="mod_button" href='/users/ips/?ip={{.Topic.IPAddress}}' title="{{lang "topic.ip_tooltip"}}" aria-label="The poster's IP is {{.Topic.IPAddress}}"><button class="username ip_label"></button></a>{{end}}
{{if .CurrentUser.Perms.ViewIPs}}<a class="mod_button" href='/users/ips/?ip={{.Topic.IP}}' title="{{lang "topic.ip_tooltip"}}" aria-label="The poster's IP is {{.Topic.IP}}"><button class="username ip_label"></button></a>{{end}}
{{end}}
<a href="/report/submit/{{.Topic.ID}}?s={{.CurrentUser.Session}}&type=topic" class="mod_button report_item" title="{{lang "topic.flag_tooltip"}}" aria-label="{{lang "topic.flag_aria"}}" rel="nofollow"><button class="username flag_label"></button></a>

View File

@ -95,7 +95,7 @@
{{if .Topic.IsClosed}}<a href='/topic/unlock/submit/{{.Topic.ID}}?s={{.CurrentUser.Session}}' class="action_button unlock_item" data-action="unlock" aria-label="{{lang "topic.unlock_aria"}}"></a>{{else}}<a href='/topic/lock/submit/{{.Topic.ID}}?s={{.CurrentUser.Session}}' class="action_button lock_item" data-action="lock" aria-label="{{lang "topic.lock_aria"}}"></a>{{end}}{{end}}
{{if .CurrentUser.Perms.PinTopic}}
{{if .Topic.Sticky}}<a href='/topic/unstick/submit/{{.Topic.ID}}?s={{.CurrentUser.Session}}' class="action_button unpin_item" data-action="unpin" aria-label="{{lang "topic.unpin_aria"}}"></a>{{else}}<a href='/topic/stick/submit/{{.Topic.ID}}?s={{.CurrentUser.Session}}' class="action_button pin_item" data-action="pin" aria-label="{{lang "topic.pin_aria"}}"></a>{{end}}{{end}}
{{if .CurrentUser.Perms.ViewIPs}}<a href="/users/ips/?ip={{.Topic.IPAddress}}" title="{{lang "topic.ip_full_tooltip"}}" class="action_button ip_item_button hide_on_big" aria-label="{{lang "topic.ip_full_aria"}}" data-action="ip"></a>{{end}}
{{if .CurrentUser.Perms.ViewIPs}}<a href="/users/ips/?ip={{.Topic.IP}}" title="{{lang "topic.ip_full_tooltip"}}" class="action_button ip_item_button hide_on_big" aria-label="{{lang "topic.ip_full_aria"}}" data-action="ip"></a>{{end}}
<a href="/report/submit/{{.Topic.ID}}?s={{.CurrentUser.Session}}&type=topic" class="action_button report_item" aria-label="{{lang "topic.report_aria"}}" data-action="report"></a>
<a href="#" class="action_button button_menu"></a>
{{end}}
@ -103,7 +103,7 @@
<div class="action_button_right">
<a class="action_button like_count hide_on_micro" aria-label="{{lang "topic.like_count_aria"}}">{{.Topic.LikeCount}}</a>
<a class="action_button created_at hide_on_mobile" title="{{abstime .Topic.CreatedAt}}">{{reltime .Topic.CreatedAt}}</a>
{{if .CurrentUser.Perms.ViewIPs}}<a href="/users/ips/?ip={{.Topic.IPAddress}}" title="{{lang "topic.ip_full_tooltip"}}" class="action_button ip_item hide_on_mobile" aria-hidden="true">{{.Topic.IPAddress}}</a>{{end}}
{{if .CurrentUser.Perms.ViewIPs}}<a href="/users/ips/?ip={{.Topic.IP}}" title="{{lang "topic.ip_full_tooltip"}}" class="action_button ip_item hide_on_mobile" aria-hidden="true">{{.Topic.IP}}</a>{{end}}
</div>
</div>
</div><div style="clear:both;"></div>

View File

@ -37,7 +37,7 @@
{{if $.CurrentUser.Perms.EditReply}}<a href="/reply/edit/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="action_button edit_item" aria-label="{{lang "topic.post_edit_aria"}}" data-action="edit"></a>{{end}}
{{end}}
{{if $.CurrentUser.Perms.DeleteReply}}<a href="/reply/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="action_button delete_item" aria-label="{{lang "topic.post_delete_aria"}}" data-action="delete"></a>{{end}}
{{if $.CurrentUser.Perms.ViewIPs}}<a href="/users/ips/?ip={{.IPAddress}}" title="{{lang "topic.ip_full_tooltip"}}" class="action_button ip_item_button hide_on_big" aria-label="{{lang "topic.ip_full_aria"}}" data-action="ip"></a>{{end}}
{{if $.CurrentUser.Perms.ViewIPs}}<a href="/users/ips/?ip={{.IP}}" title="{{lang "topic.ip_full_tooltip"}}" class="action_button ip_item_button hide_on_big" aria-label="{{lang "topic.ip_full_aria"}}" data-action="ip"></a>{{end}}
<a href="/report/submit/{{.ID}}?s={{$.CurrentUser.Session}}&amp;type=reply" class="action_button report_item" aria-label="{{lang "topic.report_aria"}}" data-action="report"></a>
<a href="#" class="action_button button_menu"></a>
{{end}}
@ -45,7 +45,7 @@
<div class="action_button_right">
<a class="action_button like_count hide_on_micro" aria-label="{{lang "topic.post_like_count_tooltip"}}">{{.LikeCount}}</a>
<a class="action_button created_at hide_on_mobile" title="{{abstime .CreatedAt}}">{{reltime .CreatedAt}}</a>
{{if $.CurrentUser.Loggedin}}{{if $.CurrentUser.Perms.ViewIPs}}<a href="/users/ips/?ip={{.IPAddress}}" title="IP Address" class="action_button ip_item hide_on_mobile" aria-hidden="true">{{.IPAddress}}</a>{{end}}{{end}}
{{if $.CurrentUser.Loggedin}}{{if $.CurrentUser.Perms.ViewIPs}}<a href="/users/ips/?ip={{.IP}}" title="IP Address" class="action_button ip_item hide_on_mobile" aria-hidden="true">{{.IP}}</a>{{end}}{{end}}
</div>
</div>
{{end}}

View File

@ -22,7 +22,7 @@
{{end}}
{{if $.CurrentUser.Perms.DeleteReply}}<a href="/reply/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "topic.post_delete_tooltip"}}" aria-label="{{lang "topic.post_delete_aria"}}"><button class="username delete_item delete_label"></button></a>{{end}}
{{if $.CurrentUser.Perms.ViewIPs}}<a class="mod_button" href='/users/ips/?ip={{.IPAddress}}' title="{{lang "topic.post_ip_tooltip"}}" aria-label="The poster's IP is {{.IPAddress}}"><button class="username ip_label"></button></a>{{end}}
{{if $.CurrentUser.Perms.ViewIPs}}<a class="mod_button" href='/users/ips/?ip={{.IP}}' title="{{lang "topic.post_ip_tooltip"}}" aria-label="The poster's IP is {{.IP}}"><button class="username ip_label"></button></a>{{end}}
<a href="/report/submit/{{.ID}}?s={{$.CurrentUser.Session}}&amp;type=reply" class="mod_button report_item" title="{{lang "topic.post_flag_tooltip"}}" aria-label="{{lang "topic.post_flag_aria"}}" rel="nofollow"><button class="username report_item flag_label"></button></a>
<a class="username hide_on_micro like_count">{{.LikeCount}}</a><a class="username hide_on_micro like_count_label" title="{{lang "topic.post_like_count_tooltip"}}"></a>