Shorten the names of the IP fields.
Shorten some other things.
This commit is contained in:
parent
31d65b91a6
commit
f1bebb7326
|
@ -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++
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -590,7 +590,7 @@ type PanelBackupPage struct {
|
|||
|
||||
type PageLogItem struct {
|
||||
Action template.HTML
|
||||
IPAddress string
|
||||
IP string
|
||||
DoneAt string
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
214
common/topic.go
214
common/topic.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
14
misc_test.go
14
misc_test.go
|
@ -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?
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}}&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}}
|
||||
|
|
|
@ -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}}&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>
|
||||
|
|
Loading…
Reference in New Issue