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"`
|
Title string `json:"title"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
CreatedBy int `json:"createdBy"`
|
CreatedBy int `json:"createdBy"`
|
||||||
IPAddress string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ESReply struct {
|
type ESReply struct {
|
||||||
|
@ -171,7 +171,7 @@ type ESReply struct {
|
||||||
TID int `json:"tid"`
|
TID int `json:"tid"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
CreatedBy int `json:"createdBy"`
|
CreatedBy int `json:"createdBy"`
|
||||||
IPAddress string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupData(client *elastic.Client) error {
|
func setupData(client *elastic.Client) error {
|
||||||
|
@ -198,12 +198,12 @@ func setupData(client *elastic.Client) error {
|
||||||
|
|
||||||
oi := 0
|
oi := 0
|
||||||
err := qgen.NewAcc().Select("topics").Cols("tid, title, content, createdBy, ipaddress").Each(func(rows *sql.Rows) error {
|
err := qgen.NewAcc().Select("topics").Cols("tid, title, content, createdBy, ipaddress").Each(func(rows *sql.Rows) error {
|
||||||
topic := ESTopic{}
|
t := ESTopic{}
|
||||||
err := rows.Scan(&topic.ID, &topic.Title, &topic.Content, &topic.CreatedBy, &topic.IPAddress)
|
err := rows.Scan(&t.ID, &t.Title, &t.Content, &t.CreatedBy, &t.IP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tin[oi] <- topic
|
tin[oi] <- t
|
||||||
if oi < 3 {
|
if oi < 3 {
|
||||||
oi++
|
oi++
|
||||||
}
|
}
|
||||||
|
@ -234,12 +234,12 @@ func setupData(client *elastic.Client) error {
|
||||||
}
|
}
|
||||||
oi := 0
|
oi := 0
|
||||||
err := qgen.NewAcc().Select("replies").Cols("rid, tid, content, createdBy, ipaddress").Each(func(rows *sql.Rows) error {
|
err := qgen.NewAcc().Select("replies").Cols("rid, tid, content, createdBy, ipaddress").Each(func(rows *sql.Rows) error {
|
||||||
reply := ESReply{}
|
r := ESReply{}
|
||||||
err := rows.Scan(&reply.ID, &reply.TID, &reply.Content, &reply.CreatedBy, &reply.IPAddress)
|
err := rows.Scan(&r.ID, &r.TID, &r.Content, &r.CreatedBy, &r.IP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rin[oi] <- reply
|
rin[oi] <- r
|
||||||
if oi < 3 {
|
if oi < 3 {
|
||||||
oi++
|
oi++
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,13 @@ type LogItem struct {
|
||||||
Action string
|
Action string
|
||||||
ElementID int
|
ElementID int
|
||||||
ElementType string
|
ElementType string
|
||||||
IPAddress string
|
IP string
|
||||||
ActorID int
|
ActorID int
|
||||||
DoneAt string
|
DoneAt string
|
||||||
}
|
}
|
||||||
|
|
||||||
type LogStore interface {
|
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
|
Count() int
|
||||||
GetOffset(offset int, perPage int) (logs []LogItem, err error)
|
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?
|
// TODO: Make a store for this?
|
||||||
func (s *SQLModLogStore) Create(action string, elementID int, elementType string, ipaddress string, actorID int) (err error) {
|
func (s *SQLModLogStore) Create(action string, elementID int, elementType string, ip string, actorID int) (err error) {
|
||||||
_, err = s.create.Exec(action, elementID, elementType, ipaddress, actorID)
|
_, err = s.create.Exec(action, elementID, elementType, ip, actorID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,20 +55,20 @@ func (s *SQLModLogStore) Count() (count int) {
|
||||||
|
|
||||||
func buildLogList(rows *sql.Rows) (logs []LogItem, err error) {
|
func buildLogList(rows *sql.Rows) (logs []LogItem, err error) {
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var log LogItem
|
var l LogItem
|
||||||
var doneAt time.Time
|
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 {
|
if err != nil {
|
||||||
return logs, err
|
return logs, err
|
||||||
}
|
}
|
||||||
log.DoneAt = doneAt.Format("2006-01-02 15:04:05")
|
l.DoneAt = doneAt.Format("2006-01-02 15:04:05")
|
||||||
logs = append(logs, log)
|
logs = append(logs, l)
|
||||||
}
|
}
|
||||||
return logs, rows.Err()
|
return logs, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *SQLModLogStore) GetOffset(offset int, perPage int) (logs []LogItem, err error) {
|
func (s *SQLModLogStore) GetOffset(offset int, perPage int) (logs []LogItem, err error) {
|
||||||
rows, err := store.getOffset.Query(offset, perPage)
|
rows, err := s.getOffset.Query(offset, perPage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return logs, err
|
return logs, err
|
||||||
}
|
}
|
||||||
|
@ -91,8 +91,8 @@ func NewAdminLogStore(acc *qgen.Accumulator) (*SQLAdminLogStore, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make a store for this?
|
// TODO: Make a store for this?
|
||||||
func (s *SQLAdminLogStore) Create(action string, elementID int, elementType string, ipaddress string, actorID int) (err error) {
|
func (s *SQLAdminLogStore) Create(action string, elementID int, elementType string, ip string, actorID int) (err error) {
|
||||||
_, err = s.create.Exec(action, elementID, elementType, ipaddress, actorID)
|
_, err = s.create.Exec(action, elementID, elementType, ip, actorID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,59 +68,59 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy gives you a non-pointer concurrency safe copy of the forum
|
// Copy gives you a non-pointer concurrency safe copy of the forum
|
||||||
func (forum *Forum) Copy() (fcopy Forum) {
|
func (f *Forum) Copy() (fcopy Forum) {
|
||||||
fcopy = *forum
|
fcopy = *f
|
||||||
return fcopy
|
return fcopy
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Write tests for this
|
// 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 == "" {
|
if name == "" {
|
||||||
name = forum.Name
|
name = f.Name
|
||||||
}
|
}
|
||||||
// TODO: Do a line sanitise? Does it matter?
|
// TODO: Do a line sanitise? Does it matter?
|
||||||
preset = strings.TrimSpace(preset)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if forum.Preset != preset && preset != "custom" && preset != "" {
|
if f.Preset != preset && preset != "custom" && preset != "" {
|
||||||
err = PermmapToQuery(PresetToPermmap(preset), forum.ID)
|
err = PermmapToQuery(PresetToPermmap(preset), f.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = Forums.Reload(forum.ID)
|
_ = Forums.Reload(f.ID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (forum *Forum) SetPreset(preset string, gid int) error {
|
func (f *Forum) SetPreset(preset string, gid int) error {
|
||||||
fperms, changed := GroupForumPresetToForumPerms(preset)
|
fperms, changed := GroupForumPresetToForumPerms(preset)
|
||||||
if changed {
|
if changed {
|
||||||
return forum.SetPerms(fperms, preset, gid)
|
return f.SetPerms(fperms, preset, gid)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Refactor this
|
// TODO: Refactor this
|
||||||
func (forum *Forum) SetPerms(fperms *ForumPerms, preset string, gid int) (err error) {
|
func (f *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})
|
err = ReplaceForumPermsForGroup(gid, map[int]string{f.ID: preset}, map[int]*ForumPerms{f.ID: fperms})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
LogError(err)
|
LogError(err)
|
||||||
return errors.New("Unable to update the permissions")
|
return errors.New("Unable to update the permissions")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add this and replaceForumPermsForGroup into a transaction?
|
// TODO: Add this and replaceForumPermsForGroup into a transaction?
|
||||||
_, err = forumStmts.setPreset.Exec("", forum.ID)
|
_, err = forumStmts.setPreset.Exec("", f.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
LogError(err)
|
LogError(err)
|
||||||
return errors.New("Unable to update the forum")
|
return errors.New("Unable to update the forum")
|
||||||
}
|
}
|
||||||
err = Forums.Reload(forum.ID)
|
err = Forums.Reload(f.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("Unable to reload forum")
|
return errors.New("Unable to reload forum")
|
||||||
}
|
}
|
||||||
err = FPStore.Reload(forum.ID)
|
err = FPStore.Reload(f.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("Unable to reload the forum permissions")
|
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) {
|
func (g *Group) ChangeRank(isAdmin bool, isMod bool, isBanned bool) (err error) {
|
||||||
_, err = groupStmts.updateGroupRank.Exec(isAdmin, isMod, isBanned, group.ID)
|
_, err = groupStmts.updateGroupRank.Exec(isAdmin, isMod, isBanned, g.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
Groups.Reload(group.ID)
|
Groups.Reload(g.ID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (group *Group) Update(name string, tag string) (err error) {
|
func (g *Group) Update(name string, tag string) (err error) {
|
||||||
_, err = groupStmts.updateGroup.Exec(name, tag, group.ID)
|
_, err = groupStmts.updateGroup.Exec(name, tag, g.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
Groups.Reload(group.ID)
|
Groups.Reload(g.ID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Please don't pass arbitrary inputs to this method
|
// 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)
|
pjson, err := json.Marshal(perms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = groupStmts.updateGroupPerms.Exec(pjson, group.ID)
|
_, err = groupStmts.updateGroupPerms.Exec(pjson, g.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return Groups.Reload(group.ID)
|
return Groups.Reload(g.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy gives you a non-pointer concurrency safe copy of the group
|
// Copy gives you a non-pointer concurrency safe copy of the group
|
||||||
func (group *Group) Copy() Group {
|
func (g *Group) Copy() Group {
|
||||||
return *group
|
return *g
|
||||||
}
|
}
|
||||||
|
|
||||||
func (group *Group) CopyPtr() (co *Group) {
|
func (g *Group) CopyPtr() (co *Group) {
|
||||||
co = new(Group)
|
co = new(Group)
|
||||||
*co = *group
|
*co = *g
|
||||||
return co
|
return co
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ type GroupStore interface {
|
||||||
Get(id int) (*Group, error)
|
Get(id int) (*Group, error)
|
||||||
GetCopy(id int) (Group, error)
|
GetCopy(id int) (Group, error)
|
||||||
Exists(id int) bool
|
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)
|
GetAll() ([]*Group, error)
|
||||||
GetRange(lower int, higher int) ([]*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
|
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
|
Email string
|
||||||
FailureReason string
|
FailureReason string
|
||||||
Success bool
|
Success bool
|
||||||
IPAddress string
|
IP string
|
||||||
DoneAt 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
|
// 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
|
// ! Retroactive updates of date are not permitted for integrity reasons
|
||||||
func (log *RegLogItem) Commit() error {
|
func (l *RegLogItem) Commit() error {
|
||||||
_, err := regLogStmts.update.Exec(log.Username, log.Email, log.FailureReason, log.Success, log.ID)
|
_, err := regLogStmts.update.Exec(l.Username, l.Email, l.FailureReason, l.Success, l.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (log *RegLogItem) Create() (id int, err error) {
|
func (l *RegLogItem) Create() (id int, err error) {
|
||||||
res, err := regLogStmts.create.Exec(log.Username, log.Email, log.FailureReason, log.Success, log.IPAddress)
|
res, err := regLogStmts.create.Exec(l.Username, l.Email, l.FailureReason, l.Success, l.IP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
id64, err := res.LastInsertId()
|
id64, err := res.LastInsertId()
|
||||||
log.ID = int(id64)
|
l.ID = int(id64)
|
||||||
return log.ID, err
|
return l.ID, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type RegLogStore interface {
|
type RegLogStore interface {
|
||||||
|
@ -79,22 +79,22 @@ func (s *SQLRegLogStore) Count() (count int) {
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *SQLRegLogStore) GetOffset(offset int, perPage int) (logs []RegLogItem, err error) {
|
func (s *SQLRegLogStore) GetOffset(offset int, perPage int) (logs []RegLogItem, err error) {
|
||||||
rows, err := store.getOffset.Query(offset, perPage)
|
rows, err := s.getOffset.Query(offset, perPage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return logs, err
|
return logs, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var log RegLogItem
|
var l RegLogItem
|
||||||
var doneAt time.Time
|
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 {
|
if err != nil {
|
||||||
return logs, err
|
return logs, err
|
||||||
}
|
}
|
||||||
log.DoneAt = doneAt.Format("2006-01-02 15:04:05")
|
l.DoneAt = doneAt.Format("2006-01-02 15:04:05")
|
||||||
logs = append(logs, log)
|
logs = append(logs, l)
|
||||||
}
|
}
|
||||||
return logs, rows.Err()
|
return logs, rows.Err()
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@ type LoginLogItem struct {
|
||||||
ID int
|
ID int
|
||||||
UID int
|
UID int
|
||||||
Success bool
|
Success bool
|
||||||
IPAddress string
|
IP string
|
||||||
DoneAt 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
|
// 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
|
// ! Retroactive updates of date are not permitted for integrity reasons
|
||||||
func (log *LoginLogItem) Commit() error {
|
func (l *LoginLogItem) Commit() error {
|
||||||
_, err := loginLogStmts.update.Exec(log.UID, log.Success, log.ID)
|
_, err := loginLogStmts.update.Exec(l.UID, l.Success, l.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (log *LoginLogItem) Create() (id int, err error) {
|
func (l *LoginLogItem) Create() (id int, err error) {
|
||||||
res, err := loginLogStmts.create.Exec(log.UID, log.Success, log.IPAddress)
|
res, err := loginLogStmts.create.Exec(l.UID, l.Success, l.IP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
id64, err := res.LastInsertId()
|
id64, err := res.LastInsertId()
|
||||||
log.ID = int(id64)
|
l.ID = int(id64)
|
||||||
return log.ID, err
|
return l.ID, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginLogStore interface {
|
type LoginLogStore interface {
|
||||||
|
@ -177,22 +177,22 @@ func (s *SQLLoginLogStore) CountUser(uid int) (count int) {
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *SQLLoginLogStore) GetOffset(uid int, offset int, perPage int) (logs []LoginLogItem, err error) {
|
func (s *SQLLoginLogStore) GetOffset(uid int, offset int, perPage int) (logs []LoginLogItem, err error) {
|
||||||
rows, err := store.getOffsetByUser.Query(uid, offset, perPage)
|
rows, err := s.getOffsetByUser.Query(uid, offset, perPage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return logs, err
|
return logs, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var log = LoginLogItem{UID: uid}
|
l := LoginLogItem{UID: uid}
|
||||||
var doneAt time.Time
|
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 {
|
if err != nil {
|
||||||
return logs, err
|
return logs, err
|
||||||
}
|
}
|
||||||
log.DoneAt = doneAt.Format("2006-01-02 15:04:05")
|
l.DoneAt = doneAt.Format("2006-01-02 15:04:05")
|
||||||
logs = append(logs, log)
|
logs = append(logs, l)
|
||||||
}
|
}
|
||||||
return logs, rows.Err()
|
return logs, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
|
@ -590,7 +590,7 @@ type PanelBackupPage struct {
|
||||||
|
|
||||||
type PageLogItem struct {
|
type PageLogItem struct {
|
||||||
Action template.HTML
|
Action template.HTML
|
||||||
IPAddress string
|
IP string
|
||||||
DoneAt 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
|
// TODO: We need a better way of overriding forum perms rather than setting them one by one
|
||||||
func OverrideForumPerms(perms *Perms, status bool) {
|
func OverrideForumPerms(p *Perms, status bool) {
|
||||||
perms.ViewTopic = status
|
p.ViewTopic = status
|
||||||
perms.LikeItem = status
|
p.LikeItem = status
|
||||||
perms.CreateTopic = status
|
p.CreateTopic = status
|
||||||
perms.EditTopic = status
|
p.EditTopic = status
|
||||||
perms.DeleteTopic = status
|
p.DeleteTopic = status
|
||||||
perms.CreateReply = status
|
p.CreateReply = status
|
||||||
perms.EditReply = status
|
p.EditReply = status
|
||||||
perms.DeleteReply = status
|
p.DeleteReply = status
|
||||||
perms.PinTopic = status
|
p.PinTopic = status
|
||||||
perms.CloseTopic = status
|
p.CloseTopic = status
|
||||||
perms.MoveTopic = status
|
p.MoveTopic = status
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterPluginPerm(name string) {
|
func RegisterPluginPerm(name string) {
|
||||||
|
|
|
@ -20,7 +20,7 @@ type ProfileReply struct {
|
||||||
LastEdit int
|
LastEdit int
|
||||||
LastEditBy int
|
LastEditBy int
|
||||||
ContentLines int
|
ContentLines int
|
||||||
IPAddress string
|
IP string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProfileReplyStmts struct {
|
type ProfileReplyStmts struct {
|
||||||
|
@ -44,18 +44,18 @@ func BlankProfileReply(id int) *ProfileReply {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Write tests for this
|
// TODO: Write tests for this
|
||||||
func (reply *ProfileReply) Delete() error {
|
func (r *ProfileReply) Delete() error {
|
||||||
_, err := profileReplyStmts.delete.Exec(reply.ID)
|
_, err := profileReplyStmts.delete.Exec(r.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (reply *ProfileReply) SetBody(content string) error {
|
func (r *ProfileReply) SetBody(content string) error {
|
||||||
content = PreparseMessage(html.UnescapeString(content))
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: We can get this from the topic store instead of a query which will always miss the cache...
|
// 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) {
|
func (r *ProfileReply) Creator() (*User, error) {
|
||||||
return Users.Get(reply.CreatedBy)
|
return Users.Get(r.CreatedBy)
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ func NewSQLProfileReplyStore(acc *qgen.Accumulator) (*SQLProfileReplyStore, erro
|
||||||
|
|
||||||
func (s *SQLProfileReplyStore) Get(id int) (*ProfileReply, error) {
|
func (s *SQLProfileReplyStore) Get(id int) (*ProfileReply, error) {
|
||||||
r := ProfileReply{ID: id}
|
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
|
return &r, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Reply Resources File
|
* Reply Resources File
|
||||||
* Copyright Azareal 2016 - 2019
|
* Copyright Azareal 2016 - 2020
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package common
|
package common
|
||||||
|
@ -37,7 +37,7 @@ type ReplyUser struct {
|
||||||
URLPrefix string
|
URLPrefix string
|
||||||
URLName string
|
URLName string
|
||||||
Level int
|
Level int
|
||||||
//IPAddress string
|
//IP string
|
||||||
//Liked bool
|
//Liked bool
|
||||||
//LikeCount int
|
//LikeCount int
|
||||||
//AttachCount int
|
//AttachCount int
|
||||||
|
@ -57,7 +57,7 @@ type Reply struct {
|
||||||
LastEdit int
|
LastEdit int
|
||||||
LastEditBy int
|
LastEditBy int
|
||||||
ContentLines int
|
ContentLines int
|
||||||
IPAddress string
|
IP string
|
||||||
Liked bool
|
Liked bool
|
||||||
LikeCount int
|
LikeCount int
|
||||||
AttachCount int
|
AttachCount int
|
||||||
|
@ -94,9 +94,9 @@ func init() {
|
||||||
|
|
||||||
// TODO: Write tests for this
|
// TODO: Write tests for this
|
||||||
// TODO: Wrap these queries in a transaction to make sure the state is consistent
|
// 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
|
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 {
|
if err != nil && err != ErrNoRows {
|
||||||
return err
|
return err
|
||||||
} else if err != ErrNoRows {
|
} else if err != ErrNoRows {
|
||||||
|
@ -104,66 +104,66 @@ func (reply *Reply) Like(uid int) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
score := 1
|
score := 1
|
||||||
_, err = replyStmts.createLike.Exec(score, reply.ID, "replies", uid)
|
_, err = replyStmts.createLike.Exec(score, r.ID, "replies", uid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = replyStmts.addLikesToReply.Exec(1, reply.ID)
|
_, err = replyStmts.addLikesToReply.Exec(1, r.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = userStmts.incrementLiked.Exec(1, uid)
|
_, err = userStmts.incrementLiked.Exec(1, uid)
|
||||||
_ = Rstore.GetCache().Remove(reply.ID)
|
_ = Rstore.GetCache().Remove(r.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (reply *Reply) Delete() error {
|
func (r *Reply) Delete() error {
|
||||||
_, err := replyStmts.delete.Exec(reply.ID)
|
_, err := replyStmts.delete.Exec(r.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// TODO: Move this bit to *Topic
|
// TODO: Move this bit to *Topic
|
||||||
_, err = replyStmts.removeRepliesFromTopic.Exec(1, reply.ParentID)
|
_, err = replyStmts.removeRepliesFromTopic.Exec(1, r.ParentID)
|
||||||
tcache := Topics.GetCache()
|
tcache := Topics.GetCache()
|
||||||
if tcache != nil {
|
if tcache != nil {
|
||||||
tcache.Remove(reply.ParentID)
|
tcache.Remove(r.ParentID)
|
||||||
}
|
}
|
||||||
_ = Rstore.GetCache().Remove(reply.ID)
|
_ = Rstore.GetCache().Remove(r.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (reply *Reply) SetPost(content string) error {
|
func (r *Reply) SetPost(content string) error {
|
||||||
topic, err := reply.Topic()
|
topic, err := r.Topic()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
content = PreparseMessage(html.UnescapeString(content))
|
content = PreparseMessage(html.UnescapeString(content))
|
||||||
parsedContent := ParseMessage(content, topic.ParentID, "forums")
|
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
|
_, 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(reply.ID)
|
_ = Rstore.GetCache().Remove(r.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Write tests for this
|
// TODO: Write tests for this
|
||||||
func (reply *Reply) SetPoll(pollID int) error {
|
func (r *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
|
_, err := replyStmts.setPoll.Exec(pollID, r.ID) // TODO: Sniff if this changed anything to see if we hit a poll
|
||||||
_ = Rstore.GetCache().Remove(reply.ID)
|
_ = Rstore.GetCache().Remove(r.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (reply *Reply) Topic() (*Topic, error) {
|
func (r *Reply) Topic() (*Topic, error) {
|
||||||
return Topics.Get(reply.ParentID)
|
return Topics.Get(r.ParentID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (reply *Reply) GetID() int {
|
func (r *Reply) GetID() int {
|
||||||
return reply.ID
|
return r.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (reply *Reply) GetTable() string {
|
func (r *Reply) GetTable() string {
|
||||||
return "replies"
|
return "replies"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy gives you a non-pointer concurrency safe copy of the reply
|
// Copy gives you a non-pointer concurrency safe copy of the reply
|
||||||
func (reply *Reply) Copy() Reply {
|
func (r *Reply) Copy() Reply {
|
||||||
return *reply
|
return *r
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ func (s *SQLReplyStore) Get(id int) (*Reply, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r = &Reply{ID: id}
|
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 {
|
if err == nil {
|
||||||
_ = s.cache.Set(r)
|
_ = s.cache.Set(r)
|
||||||
}
|
}
|
||||||
|
@ -50,9 +50,9 @@ func (s *SQLReplyStore) Get(id int) (*Reply, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Write a test for this
|
// 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)
|
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 {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,8 @@ func (s *SQLReplyStore) Create(topic *Topic, content string, ipaddress string, u
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
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
|
// TODO: Write a test for this
|
||||||
|
|
214
common/topic.go
214
common/topic.go
|
@ -38,7 +38,7 @@ type Topic struct {
|
||||||
LastReplyID int
|
LastReplyID int
|
||||||
ParentID int
|
ParentID int
|
||||||
Status string // Deprecated. Marked for removal.
|
Status string // Deprecated. Marked for removal.
|
||||||
IPAddress string
|
IP string
|
||||||
ViewCount int64
|
ViewCount int64
|
||||||
PostCount int
|
PostCount int
|
||||||
LikeCount int
|
LikeCount int
|
||||||
|
@ -64,7 +64,7 @@ type TopicUser struct {
|
||||||
LastReplyID int
|
LastReplyID int
|
||||||
ParentID int
|
ParentID int
|
||||||
Status string // Deprecated. Marked for removal.
|
Status string // Deprecated. Marked for removal.
|
||||||
IPAddress string
|
IP string
|
||||||
ViewCount int64
|
ViewCount int64
|
||||||
PostCount int
|
PostCount int
|
||||||
LikeCount int
|
LikeCount int
|
||||||
|
@ -106,7 +106,7 @@ type TopicsRow struct {
|
||||||
LastReplyID int
|
LastReplyID int
|
||||||
ParentID int
|
ParentID int
|
||||||
Status string // Deprecated. Marked for removal. -Is there anything we could use it for?
|
Status string // Deprecated. Marked for removal. -Is there anything we could use it for?
|
||||||
IPAddress string
|
IP string
|
||||||
ViewCount int64
|
ViewCount int64
|
||||||
PostCount int
|
PostCount int
|
||||||
LikeCount int
|
LikeCount int
|
||||||
|
@ -151,8 +151,8 @@ type WsTopicsRow struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Can we get the client side to render the relative times instead?
|
// TODO: Can we get the client side to render the relative times instead?
|
||||||
func (row *TopicsRow) WebSockets() *WsTopicsRow {
|
func (r *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}
|
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?
|
// TODO: Stop relying on so many struct types?
|
||||||
|
@ -165,12 +165,12 @@ func (t *Topic) TopicsRow() *TopicsRow {
|
||||||
forumName := ""
|
forumName := ""
|
||||||
forumLink := ""
|
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
|
// ! Some data may be lost in the conversion
|
||||||
func (t *TopicsRow) Topic() *Topic {
|
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
|
// ! 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
|
// Flush the topic out of the cache
|
||||||
// ? - We do a CacheRemove() here instead of mutating the pointer to avoid creating a race condition
|
// ? - 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()
|
tcache := Topics.GetCache()
|
||||||
if tcache != nil {
|
if tcache != nil {
|
||||||
tcache.Remove(topic.ID)
|
tcache.Remove(t.ID)
|
||||||
}
|
}
|
||||||
TopicListThaw.Thaw()
|
TopicListThaw.Thaw()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Write a test for this
|
// TODO: Write a test for this
|
||||||
func (topic *Topic) AddReply(rid int, uid int) (err error) {
|
func (t *Topic) AddReply(rid int, uid int) (err error) {
|
||||||
_, err = topicStmts.addReplies.Exec(1, uid, topic.ID)
|
_, err = topicStmts.addReplies.Exec(1, uid, t.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = topicStmts.updateLastReply.Exec(rid, rid, topic.ID)
|
_, err = topicStmts.updateLastReply.Exec(rid, rid, t.ID)
|
||||||
topic.cacheRemove()
|
t.cacheRemove()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (topic *Topic) Lock() (err error) {
|
func (t *Topic) Lock() (err error) {
|
||||||
_, err = topicStmts.lock.Exec(topic.ID)
|
_, err = topicStmts.lock.Exec(t.ID)
|
||||||
topic.cacheRemove()
|
t.cacheRemove()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (topic *Topic) Unlock() (err error) {
|
func (t *Topic) Unlock() (err error) {
|
||||||
_, err = topicStmts.unlock.Exec(topic.ID)
|
_, err = topicStmts.unlock.Exec(t.ID)
|
||||||
topic.cacheRemove()
|
t.cacheRemove()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (topic *Topic) MoveTo(destForum int) (err error) {
|
func (t *Topic) MoveTo(destForum int) (err error) {
|
||||||
_, err = topicStmts.moveTo.Exec(destForum, topic.ID)
|
_, err = topicStmts.moveTo.Exec(destForum, t.ID)
|
||||||
topic.cacheRemove()
|
t.cacheRemove()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = Attachments.MoveTo(destForum, topic.ID, "topics")
|
err = Attachments.MoveTo(destForum, t.ID, "topics")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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.
|
// 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) {
|
func (t *Topic) Stick() (err error) {
|
||||||
_, err = topicStmts.stick.Exec(topic.ID)
|
_, err = topicStmts.stick.Exec(t.ID)
|
||||||
topic.cacheRemove()
|
t.cacheRemove()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (topic *Topic) Unstick() (err error) {
|
func (t *Topic) Unstick() (err error) {
|
||||||
_, err = topicStmts.unstick.Exec(topic.ID)
|
_, err = topicStmts.unstick.Exec(t.ID)
|
||||||
topic.cacheRemove()
|
t.cacheRemove()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Test this
|
// TODO: Test this
|
||||||
// TODO: Use a transaction for 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
|
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 {
|
if err != nil && err != ErrNoRows {
|
||||||
return err
|
return err
|
||||||
} else if err != ErrNoRows {
|
} else if err != ErrNoRows {
|
||||||
return ErrAlreadyLiked
|
return ErrAlreadyLiked
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = topicStmts.createLike.Exec(score, topic.ID, "topics", uid)
|
_, err = topicStmts.createLike.Exec(score, t.ID, "topics", uid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = topicStmts.addLikesToTopic.Exec(1, topic.ID)
|
_, err = topicStmts.addLikesToTopic.Exec(1, t.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = userStmts.incrementLiked.Exec(1, uid)
|
_, err = userStmts.incrementLiked.Exec(1, uid)
|
||||||
topic.cacheRemove()
|
t.cacheRemove()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement this
|
// TODO: Implement this
|
||||||
func (topic *Topic) Unlike(uid int) error {
|
func (t *Topic) Unlike(uid int) error {
|
||||||
topic.cacheRemove()
|
t.cacheRemove()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use a transaction here
|
// TODO: Use a transaction here
|
||||||
func (topic *Topic) Delete() error {
|
func (t *Topic) Delete() error {
|
||||||
topicCreator, err := Users.Get(topic.CreatedBy)
|
topicCreator, err := Users.Get(t.CreatedBy)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
wcount := WordCount(topic.Content)
|
wcount := WordCount(t.Content)
|
||||||
err = topicCreator.DecreasePostStats(wcount, true)
|
err = topicCreator.DecreasePostStats(wcount, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -340,26 +340,26 @@ func (topic *Topic) Delete() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = Forums.RemoveTopic(topic.ParentID)
|
err = Forums.RemoveTopic(t.ParentID)
|
||||||
if err != nil && err != ErrNoRows {
|
if err != nil && err != ErrNoRows {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = topicStmts.delete.Exec(topic.ID)
|
_, err = topicStmts.delete.Exec(t.ID)
|
||||||
topic.cacheRemove()
|
t.cacheRemove()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = topicStmts.deleteActivitySubs.Exec(topic.ID)
|
_, err = topicStmts.deleteActivitySubs.Exec(t.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = topicStmts.deleteActivity.Exec(topic.ID)
|
_, err = topicStmts.deleteActivity.Exec(t.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Write tests for this
|
// 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))
|
name = SanitiseSingleLine(html.UnescapeString(name))
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return ErrNoTitle
|
return ErrNoTitle
|
||||||
|
@ -370,25 +370,25 @@ func (topic *Topic) Update(name string, content string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
content = PreparseMessage(html.UnescapeString(content))
|
content = PreparseMessage(html.UnescapeString(content))
|
||||||
parsedContent := ParseMessage(content, topic.ParentID, "forums")
|
parsedContent := ParseMessage(content, t.ParentID, "forums")
|
||||||
_, err := topicStmts.edit.Exec(name, content, parsedContent, topic.ID)
|
_, err := topicStmts.edit.Exec(name, content, parsedContent, t.ID)
|
||||||
topic.cacheRemove()
|
t.cacheRemove()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (topic *Topic) SetPoll(pollID int) error {
|
func (t *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
|
_, err := topicStmts.setPoll.Exec(pollID, t.ID) // TODO: Sniff if this changed anything to see if we hit an existing poll
|
||||||
topic.cacheRemove()
|
t.cacheRemove()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Have this go through the ReplyStore?
|
// TODO: Have this go through the ReplyStore?
|
||||||
func (topic *Topic) CreateActionReply(action string, ipaddress string, uid int) (err error) {
|
func (t *Topic) CreateActionReply(action string, ip string, uid int) (err error) {
|
||||||
res, err := topicStmts.createAction.Exec(topic.ID, action, ipaddress, uid)
|
res, err := topicStmts.createAction.Exec(t.ID, action, ip, uid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = topicStmts.addReplies.Exec(1, uid, topic.ID)
|
_, err = topicStmts.addReplies.Exec(1, uid, t.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -397,8 +397,8 @@ func (topic *Topic) CreateActionReply(action string, ipaddress string, uid int)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rid := int(lid)
|
rid := int(lid)
|
||||||
_, err = topicStmts.updateLastReply.Exec(rid, rid, topic.ID)
|
_, err = topicStmts.updateLastReply.Exec(rid, rid, t.ID)
|
||||||
topic.cacheRemove()
|
t.cacheRemove()
|
||||||
// ? - Update the last topic cache for the parent forum?
|
// ? - Update the last topic cache for the parent forum?
|
||||||
return err
|
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
|
// 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
|
var likedMap map[int]int
|
||||||
if user.Liked > 0 {
|
if user.Liked > 0 {
|
||||||
likedMap = make(map[int]int)
|
likedMap = make(map[int]int)
|
||||||
}
|
}
|
||||||
var likedQueryList = []int{user.ID}
|
likedQueryList := []int{user.ID}
|
||||||
|
|
||||||
var attachMap map[int]int
|
var attachMap map[int]int
|
||||||
if user.Perms.EditReply {
|
if user.Perms.EditReply {
|
||||||
attachMap = make(map[int]int)
|
attachMap = make(map[int]int)
|
||||||
}
|
}
|
||||||
var attachQueryList = []int{}
|
attachQueryList := []int{}
|
||||||
|
|
||||||
var rid int
|
var rid int
|
||||||
if len(topic.Rids) > 0 {
|
if len(t.Rids) > 0 {
|
||||||
//log.Print("have rid")
|
//log.Print("have rid")
|
||||||
rid = topic.Rids[0]
|
rid = t.Rids[0]
|
||||||
}
|
}
|
||||||
re, err := Rstore.GetCache().Get(rid)
|
re, err := Rstore.GetCache().Get(rid)
|
||||||
ucache := Users.GetCache()
|
ucache := Users.GetCache()
|
||||||
|
@ -524,7 +524,7 @@ func (topic *TopicUser) Replies(offset int, pFrag int, user *User) (rlist []*Rep
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
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.
|
// TODO: Do this more efficiently by avoiding the allocations entirely in ParseMessage, if there's nothing to do.
|
||||||
if reply.ContentHtml == reply.Content {
|
if reply.ContentHtml == reply.Content {
|
||||||
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)
|
hTbl.VhookNoRet("topic_reply_row_assign", &rlist, &reply)
|
||||||
rlist = append(rlist, reply)
|
rlist = append(rlist, reply)
|
||||||
} else {
|
} else {
|
||||||
rows, err := topicStmts.getReplies.Query(topic.ID, offset, Config.ItemsPerPage)
|
rows, err := topicStmts.getReplies.Query(t.ID, offset, Config.ItemsPerPage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
@ -557,7 +557,7 @@ func (topic *TopicUser) Replies(offset int, pFrag int, user *User) (rlist []*Rep
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
reply = &ReplyUser{}
|
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 {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
@ -566,7 +566,7 @@ func (topic *TopicUser) Replies(offset int, pFrag int, user *User) (rlist []*Rep
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
reply.ContentHtml = ParseMessage(reply.Content, topic.ParentID, "forums")
|
reply.ContentHtml = ParseMessage(reply.Content, t.ParentID, "forums")
|
||||||
|
|
||||||
if reply.ID == pFrag {
|
if reply.ID == pFrag {
|
||||||
ogdesc = reply.Content
|
ogdesc = reply.Content
|
||||||
|
@ -627,28 +627,28 @@ func (topic *TopicUser) Replies(offset int, pFrag int, user *User) (rlist []*Rep
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Test this
|
// TODO: Test this
|
||||||
func (topic *Topic) Author() (*User, error) {
|
func (t *Topic) Author() (*User, error) {
|
||||||
return Users.Get(topic.CreatedBy)
|
return Users.Get(t.CreatedBy)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (topic *Topic) GetID() int {
|
func (t *Topic) GetID() int {
|
||||||
return topic.ID
|
return t.ID
|
||||||
}
|
}
|
||||||
func (topic *Topic) GetTable() string {
|
func (t *Topic) GetTable() string {
|
||||||
return "topics"
|
return "topics"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy gives you a non-pointer concurrency safe copy of the topic
|
// Copy gives you a non-pointer concurrency safe copy of the topic
|
||||||
func (topic *Topic) Copy() Topic {
|
func (t *Topic) Copy() Topic {
|
||||||
return *topic
|
return *t
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Load LastReplyAt and LastReplyID?
|
// TODO: Load LastReplyAt and LastReplyID?
|
||||||
func TopicByReplyID(rid int) (*Topic, error) {
|
func TopicByReplyID(rid int) (*Topic, error) {
|
||||||
topic := Topic{ID: 0}
|
t := 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)
|
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)
|
||||||
topic.Link = BuildTopicURL(NameToSlug(topic.Title), topic.ID)
|
t.Link = BuildTopicURL(NameToSlug(t.Title), t.ID)
|
||||||
return &topic, err
|
return &t, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Refactor the caller to take a Topic and a User rather than a combined TopicUser
|
// 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}
|
tu = TopicUser{ID: tid}
|
||||||
// TODO: This misses some important bits...
|
// 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.Avatar, tu.MicroAvatar = BuildAvatar(tu.CreatedBy, tu.Avatar)
|
||||||
tu.Link = BuildTopicURL(NameToSlug(tu.Title), tu.ID)
|
tu.Link = BuildTopicURL(NameToSlug(tu.Title), tu.ID)
|
||||||
tu.UserLink = BuildProfileURL(NameToSlug(tu.CreatedByName), tu.CreatedBy)
|
tu.UserLink = BuildProfileURL(NameToSlug(tu.CreatedByName), tu.CreatedBy)
|
||||||
tu.Tag = Groups.DirtyGet(tu.Group).Tag
|
tu.Tag = Groups.DirtyGet(tu.Group).Tag
|
||||||
|
|
||||||
if tcache != nil {
|
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)
|
//log.Printf("theTopic: %+v\n", theTopic)
|
||||||
_ = tcache.Set(&theTopic)
|
_ = tcache.Set(&theTopic)
|
||||||
}
|
}
|
||||||
return tu, err
|
return tu, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyTopicToTopicUser(topic *Topic, user *User) (tu TopicUser) {
|
func copyTopicToTopicUser(t *Topic, u *User) (tu TopicUser) {
|
||||||
tu.UserLink = user.Link
|
tu.UserLink = u.Link
|
||||||
tu.CreatedByName = user.Name
|
tu.CreatedByName = u.Name
|
||||||
tu.Group = user.Group
|
tu.Group = u.Group
|
||||||
tu.Avatar = user.Avatar
|
tu.Avatar = u.Avatar
|
||||||
tu.MicroAvatar = user.MicroAvatar
|
tu.MicroAvatar = u.MicroAvatar
|
||||||
tu.URLPrefix = user.URLPrefix
|
tu.URLPrefix = u.URLPrefix
|
||||||
tu.URLName = user.URLName
|
tu.URLName = u.URLName
|
||||||
tu.Level = user.Level
|
tu.Level = u.Level
|
||||||
|
|
||||||
tu.ID = topic.ID
|
tu.ID = t.ID
|
||||||
tu.Link = topic.Link
|
tu.Link = t.Link
|
||||||
tu.Title = topic.Title
|
tu.Title = t.Title
|
||||||
tu.Content = topic.Content
|
tu.Content = t.Content
|
||||||
tu.CreatedBy = topic.CreatedBy
|
tu.CreatedBy = t.CreatedBy
|
||||||
tu.IsClosed = topic.IsClosed
|
tu.IsClosed = t.IsClosed
|
||||||
tu.Sticky = topic.Sticky
|
tu.Sticky = t.Sticky
|
||||||
tu.CreatedAt = topic.CreatedAt
|
tu.CreatedAt = t.CreatedAt
|
||||||
tu.LastReplyAt = topic.LastReplyAt
|
tu.LastReplyAt = t.LastReplyAt
|
||||||
tu.LastReplyBy = topic.LastReplyBy
|
tu.LastReplyBy = t.LastReplyBy
|
||||||
tu.ParentID = topic.ParentID
|
tu.ParentID = t.ParentID
|
||||||
tu.IPAddress = topic.IPAddress
|
tu.IP = t.IP
|
||||||
tu.ViewCount = topic.ViewCount
|
tu.ViewCount = t.ViewCount
|
||||||
tu.PostCount = topic.PostCount
|
tu.PostCount = t.PostCount
|
||||||
tu.LikeCount = topic.LikeCount
|
tu.LikeCount = t.LikeCount
|
||||||
tu.AttachCount = topic.AttachCount
|
tu.AttachCount = t.AttachCount
|
||||||
tu.Poll = topic.Poll
|
tu.Poll = t.Poll
|
||||||
tu.Data = topic.Data
|
tu.Data = t.Data
|
||||||
tu.Rids = topic.Rids
|
tu.Rids = t.Rids
|
||||||
|
|
||||||
return tu
|
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
|
// BypassGet will always bypass the cache and pull the topic directly from the database
|
||||||
func (s *DefaultTopicStore) BypassGet(id int) (*Topic, error) {
|
func (s *DefaultTopicStore) BypassGet(id int) (*Topic, error) {
|
||||||
t := &Topic{ID: id}
|
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 {
|
if err == nil {
|
||||||
t.Link = BuildTopicURL(NameToSlug(t.Title), id)
|
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() {
|
for rows.Next() {
|
||||||
t := &Topic{}
|
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 {
|
if err != nil {
|
||||||
return list, err
|
return list, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,11 +40,11 @@ func NewMemoryUserCache(capacity int) *MemoryUserCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Avoid deallocating topic list users
|
// TODO: Avoid deallocating topic list users
|
||||||
func (mus *MemoryUserCache) DeallocOverflow(evictPriority bool) (evicted int) {
|
func (s *MemoryUserCache) DeallocOverflow(evictPriority bool) (evicted int) {
|
||||||
var toEvict = make([]int, 10)
|
toEvict := make([]int, 10)
|
||||||
var evIndex = 0
|
evIndex := 0
|
||||||
mus.RLock()
|
s.RLock()
|
||||||
for _, user := range mus.items {
|
for _, user := range s.items {
|
||||||
if /*user.LastActiveAt < lastActiveCutoff && */ user.Score == 0 && !user.IsMod {
|
if /*user.LastActiveAt < lastActiveCutoff && */ user.Score == 0 && !user.IsMod {
|
||||||
if EnableWebsockets && WsHub.HasUser(user.ID) {
|
if EnableWebsockets && WsHub.HasUser(user.ID) {
|
||||||
continue
|
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
|
// Clear some of the less active users now with a bit more aggressiveness
|
||||||
if evIndex == 0 && evictPriority {
|
if evIndex == 0 && evictPriority {
|
||||||
toEvict = make([]int, 20)
|
toEvict = make([]int, 20)
|
||||||
mus.RLock()
|
s.RLock()
|
||||||
for _, user := range mus.items {
|
for _, user := range s.items {
|
||||||
if user.Score < 100 && !user.IsMod {
|
if user.Score < 100 && !user.IsMod {
|
||||||
if EnableWebsockets && WsHub.HasUser(user.ID) {
|
if EnableWebsockets && WsHub.HasUser(user.ID) {
|
||||||
continue
|
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
|
// 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 {
|
for i, uid := range toEvict {
|
||||||
if uid == 0 {
|
if uid == 0 {
|
||||||
lastZero = i
|
lastZero = i
|
||||||
|
@ -88,15 +88,15 @@ func (mus *MemoryUserCache) DeallocOverflow(evictPriority bool) (evicted int) {
|
||||||
toEvict = toEvict[:lastZero]
|
toEvict = toEvict[:lastZero]
|
||||||
}
|
}
|
||||||
|
|
||||||
mus.BulkRemove(toEvict)
|
s.BulkRemove(toEvict)
|
||||||
return len(toEvict)
|
return len(toEvict)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get fetches a user by ID. Returns ErrNoRows if not present.
|
// Get fetches a user by ID. Returns ErrNoRows if not present.
|
||||||
func (mus *MemoryUserCache) Get(id int) (*User, error) {
|
func (s *MemoryUserCache) Get(id int) (*User, error) {
|
||||||
mus.RLock()
|
s.RLock()
|
||||||
item, ok := mus.items[id]
|
item, ok := s.items[id]
|
||||||
mus.RUnlock()
|
s.RUnlock()
|
||||||
if ok {
|
if ok {
|
||||||
return item, nil
|
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.
|
// 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))
|
list = make([]*User, len(ids))
|
||||||
mus.RLock()
|
s.RLock()
|
||||||
for i, id := range ids {
|
for i, id := range ids {
|
||||||
list[i] = mus.items[id]
|
list[i] = s.items[id]
|
||||||
}
|
}
|
||||||
mus.RUnlock()
|
s.RUnlock()
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUnsafe fetches a user by ID. Returns ErrNoRows if not present. THIS METHOD IS NOT THREAD-SAFE.
|
// 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) {
|
func (s *MemoryUserCache) GetUnsafe(id int) (*User, error) {
|
||||||
item, ok := mus.items[id]
|
item, ok := s.items[id]
|
||||||
if ok {
|
if ok {
|
||||||
return item, nil
|
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.
|
// 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 {
|
func (s *MemoryUserCache) Set(item *User) error {
|
||||||
mus.Lock()
|
s.Lock()
|
||||||
user, ok := mus.items[item.ID]
|
user, ok := s.items[item.ID]
|
||||||
if ok {
|
if ok {
|
||||||
mus.Unlock()
|
s.Unlock()
|
||||||
*user = *item
|
*user = *item
|
||||||
} else if int(mus.length) >= mus.capacity {
|
} else if int(s.length) >= s.capacity {
|
||||||
mus.Unlock()
|
s.Unlock()
|
||||||
return ErrStoreCapacityOverflow
|
return ErrStoreCapacityOverflow
|
||||||
} else {
|
} else {
|
||||||
mus.items[item.ID] = item
|
s.items[item.ID] = item
|
||||||
mus.Unlock()
|
s.Unlock()
|
||||||
atomic.AddInt64(&mus.length, 1)
|
atomic.AddInt64(&s.length, 1)
|
||||||
}
|
}
|
||||||
return nil
|
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.
|
// 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?
|
// ? Is this redundant if we have Set? Are the efficiency wins worth this? Is this even used?
|
||||||
func (mus *MemoryUserCache) Add(item *User) error {
|
func (s *MemoryUserCache) Add(item *User) error {
|
||||||
mus.Lock()
|
s.Lock()
|
||||||
if int(mus.length) >= mus.capacity {
|
if int(s.length) >= s.capacity {
|
||||||
mus.Unlock()
|
s.Unlock()
|
||||||
return ErrStoreCapacityOverflow
|
return ErrStoreCapacityOverflow
|
||||||
}
|
}
|
||||||
mus.items[item.ID] = item
|
s.items[item.ID] = item
|
||||||
mus.length = int64(len(mus.items))
|
s.length = int64(len(s.items))
|
||||||
mus.Unlock()
|
s.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddUnsafe is the unsafe version of Add. May return a capacity overflow error. THIS METHOD IS NOT THREAD-SAFE.
|
// 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 {
|
func (s *MemoryUserCache) AddUnsafe(item *User) error {
|
||||||
if int(mus.length) >= mus.capacity {
|
if int(s.length) >= s.capacity {
|
||||||
return ErrStoreCapacityOverflow
|
return ErrStoreCapacityOverflow
|
||||||
}
|
}
|
||||||
mus.items[item.ID] = item
|
s.items[item.ID] = item
|
||||||
mus.length = int64(len(mus.items))
|
s.length = int64(len(s.items))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes a user from the cache by ID, if they exist. Returns ErrNoRows if no items exist.
|
// 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 {
|
func (s *MemoryUserCache) Remove(id int) error {
|
||||||
mus.Lock()
|
s.Lock()
|
||||||
_, ok := mus.items[id]
|
_, ok := s.items[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
mus.Unlock()
|
s.Unlock()
|
||||||
return ErrNoRows
|
return ErrNoRows
|
||||||
}
|
}
|
||||||
delete(mus.items, id)
|
delete(s.items, id)
|
||||||
mus.Unlock()
|
s.Unlock()
|
||||||
atomic.AddInt64(&mus.length, -1)
|
atomic.AddInt64(&s.length, -1)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveUnsafe is the unsafe version of Remove. THIS METHOD IS NOT THREAD-SAFE.
|
// RemoveUnsafe is the unsafe version of Remove. THIS METHOD IS NOT THREAD-SAFE.
|
||||||
func (mus *MemoryUserCache) RemoveUnsafe(id int) error {
|
func (s *MemoryUserCache) RemoveUnsafe(id int) error {
|
||||||
_, ok := mus.items[id]
|
_, ok := s.items[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrNoRows
|
return ErrNoRows
|
||||||
}
|
}
|
||||||
delete(mus.items, id)
|
delete(s.items, id)
|
||||||
atomic.AddInt64(&mus.length, -1)
|
atomic.AddInt64(&s.length, -1)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mus *MemoryUserCache) BulkRemove(ids []int) {
|
func (s *MemoryUserCache) BulkRemove(ids []int) {
|
||||||
var rCount int64
|
var rCount int64
|
||||||
mus.Lock()
|
s.Lock()
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
_, ok := mus.items[id]
|
_, ok := s.items[id]
|
||||||
if ok {
|
if ok {
|
||||||
delete(mus.items, id)
|
delete(s.items, id)
|
||||||
rCount++
|
rCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mus.Unlock()
|
s.Unlock()
|
||||||
atomic.AddInt64(&mus.length, -rCount)
|
atomic.AddInt64(&s.length, -rCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush removes all the users from the cache, useful for tests.
|
// Flush removes all the users from the cache, useful for tests.
|
||||||
func (mus *MemoryUserCache) Flush() {
|
func (s *MemoryUserCache) Flush() {
|
||||||
mus.Lock()
|
s.Lock()
|
||||||
mus.items = make(map[int]*User)
|
s.items = make(map[int]*User)
|
||||||
mus.length = 0
|
s.length = 0
|
||||||
mus.Unlock()
|
s.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ! Is this concurrent?
|
// ! Is this concurrent?
|
||||||
// Length returns the number of users in the memory cache
|
// Length returns the number of users in the memory cache
|
||||||
func (mus *MemoryUserCache) Length() int {
|
func (s *MemoryUserCache) Length() int {
|
||||||
return int(mus.length)
|
return int(s.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCapacity sets the maximum number of users which this cache can hold
|
// 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
|
// 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
|
// GetCapacity returns the maximum number of users this cache can hold
|
||||||
func (mus *MemoryUserCache) GetCapacity() int {
|
func (s *MemoryUserCache) GetCapacity() int {
|
||||||
return mus.capacity
|
return s.capacity
|
||||||
}
|
}
|
||||||
|
|
|
@ -793,7 +793,7 @@ func BenchmarkQueryTopicParallel(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
var tu c.TopicUser
|
var tu c.TopicUser
|
||||||
for pb.Next() {
|
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 {
|
if err == ErrNoRows {
|
||||||
log.Fatal("No rows found!")
|
log.Fatal("No rows found!")
|
||||||
return
|
return
|
||||||
|
@ -822,7 +822,7 @@ func BenchmarkQueryPreparedTopicParallel(b *testing.B) {
|
||||||
defer getTopicUser.Close()
|
defer getTopicUser.Close()
|
||||||
|
|
||||||
for pb.Next() {
|
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 {
|
if err == ErrNoRows {
|
||||||
b.Fatal("No rows found!")
|
b.Fatal("No rows found!")
|
||||||
return
|
return
|
||||||
|
@ -878,7 +878,7 @@ func BenchmarkQueriesSerial(b *testing.B) {
|
||||||
var tu c.TopicUser
|
var tu c.TopicUser
|
||||||
b.Run("topic", func(b *testing.B) {
|
b.Run("topic", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
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 {
|
if err == ErrNoRows {
|
||||||
b.Fatal("No rows found!")
|
b.Fatal("No rows found!")
|
||||||
return
|
return
|
||||||
|
@ -907,7 +907,7 @@ func BenchmarkQueriesSerial(b *testing.B) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
var replyItem c.ReplyUser
|
var r c.ReplyUser
|
||||||
var isSuperAdmin bool
|
var isSuperAdmin bool
|
||||||
var group int
|
var group int
|
||||||
b.Run("topic_replies_scan", func(b *testing.B) {
|
b.Run("topic_replies_scan", func(b *testing.B) {
|
||||||
|
@ -918,7 +918,7 @@ func BenchmarkQueriesSerial(b *testing.B) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for rows.Next() {
|
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 {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
return
|
return
|
||||||
|
|
14
misc_test.go
14
misc_test.go
|
@ -497,7 +497,7 @@ func topicStoreTest(t *testing.T, newID int) {
|
||||||
return ""
|
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)
|
topic, err = c.Topics.Get(tid)
|
||||||
recordMustExist(t, err, fmt.Sprintf("Couldn't find TID #%d", 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))
|
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.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.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.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.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.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)))
|
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)
|
_, err = c.Rstore.Get(0)
|
||||||
recordMustNotExist(t, err, "RID #0 shouldn't exist")
|
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)
|
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.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.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.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.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)
|
reply, err := c.Rstore.Get(rid)
|
||||||
replyTest2(reply, err, rid, parentID, createdBy, content, ip)
|
replyTest2(reply, err, rid, parentID, createdBy, content, ip)
|
||||||
reply, err = c.Rstore.GetCache().Get(rid)
|
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.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.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.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()
|
err = profileReply.Delete()
|
||||||
expectNilErr(t, err)
|
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.Action == "something", "log.Action is not something")
|
||||||
expect(t, log.ElementID == 0, "log.ElementID is not 0")
|
expect(t, log.ElementID == 0, "log.ElementID is not 0")
|
||||||
expect(t, log.ElementType == "bumblefly", "log.ElementType is not bumblefly")
|
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")
|
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?
|
// 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"))
|
username := c.SanitiseSingleLine(r.PostFormValue("username"))
|
||||||
uid, err, requiresExtraAuth := c.Auth.Authenticate(username, r.PostFormValue("password"))
|
uid, err, requiresExtraAuth := c.Auth.Authenticate(username, r.PostFormValue("password"))
|
||||||
if err != nil {
|
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?
|
// 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()
|
_, err := logItem.Create()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.InternalError(err, w, r)
|
return c.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return c.LocalError(err.Error(), w, r, user)
|
return c.LocalError(err.Error(), w, r, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Take 2FA into account
|
// 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()
|
_, err = logItem.Create()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.InternalError(err, w, r)
|
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()
|
_, err = regLog.Create()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.InternalError(err, w, r)
|
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 {
|
if err != nil {
|
||||||
return c.InternalError(err, w, r)
|
return c.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
var llist = make([]c.PageLogItem, len(logs))
|
llist := make([]c.PageLogItem, len(logs))
|
||||||
for index, log := range logs {
|
for index, log := range logs {
|
||||||
actor := handleUnknownUser(c.Users.Get(log.ActorID))
|
actor := handleUnknownUser(c.Users.Get(log.ActorID))
|
||||||
action := modlogsElementType(log.Action, log.ElementType, log.ElementID, actor)
|
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)
|
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 {
|
if err != nil {
|
||||||
return c.InternalError(err, w, r)
|
return c.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
var llist = make([]c.PageLogItem, len(logs))
|
llist := make([]c.PageLogItem, len(logs))
|
||||||
for index, log := range logs {
|
for index, log := range logs {
|
||||||
actor := handleUnknownUser(c.Users.Get(log.ActorID))
|
actor := handleUnknownUser(c.Users.Get(log.ActorID))
|
||||||
action := modlogsElementType(log.Action, log.ElementType, log.ElementID, actor)
|
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)
|
pageList := c.Paginate(page, lastPage, 5)
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<div class="rowitem{{if not .Success}} bg_red{{end}}">
|
<div class="rowitem{{if not .Success}} bg_red{{end}}">
|
||||||
<span class="to_left">
|
<span class="to_left">
|
||||||
<span>{{if .Success}}{{lang "account_logins_success"}}{{else}}{{lang "account_logins_failure"}}"{{end}}</span><br />
|
<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>
|
||||||
<span class="to_right">
|
<span class="to_right">
|
||||||
<span title="{{.DoneAt}}">{{.DoneAt}}</span>
|
<span title="{{.DoneAt}}">{{.DoneAt}}</span>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<div class="rowitem panel_compactrow">
|
<div class="rowitem panel_compactrow">
|
||||||
<span class="to_left">
|
<span class="to_left">
|
||||||
<span>{{.Action}}</span>
|
<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>
|
||||||
<span class="to_right">
|
<span class="to_right">
|
||||||
<span title="{{.DoneAt}}">{{.DoneAt}}</span>
|
<span title="{{.DoneAt}}">{{.DoneAt}}</span>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<div class="rowitem panel_compactrow">
|
<div class="rowitem panel_compactrow">
|
||||||
<span class="to_left">
|
<span class="to_left">
|
||||||
<span>{{.Action}}</span>
|
<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>
|
||||||
<span class="to_right">
|
<span class="to_right">
|
||||||
<span title="{{.DoneAt}}">{{.DoneAt}}</span>
|
<span title="{{.DoneAt}}">{{.DoneAt}}</span>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<div class="rowitem panel_compactrow{{if not .Success}} bg_red{{end}}">
|
<div class="rowitem panel_compactrow{{if not .Success}} bg_red{{end}}">
|
||||||
<span class="to_left{{if not .Success}} panel_registration_attempt{{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>
|
<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>
|
||||||
<span class="to_right">
|
<span class="to_right">
|
||||||
<span title="{{.DoneAt}}">{{.DoneAt}}</span>
|
<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.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.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}}
|
{{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>
|
<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 .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 .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 .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="/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>
|
<a href="#" class="action_button button_menu"></a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -103,7 +103,7 @@
|
||||||
<div class="action_button_right">
|
<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 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>
|
<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>
|
||||||
</div><div style="clear:both;"></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}}
|
{{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}}
|
{{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.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="/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>
|
<a href="#" class="action_button button_menu"></a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
<div class="action_button_right">
|
<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 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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
{{end}}
|
{{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.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 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>
|
<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