Add the disk and database sections to the debug page.

Add the ActivityStream interface to abstract Get, Add and Count.
Rename the GlobalCount methods to Count for simplicity.
Simplify the variable names in the Count methods.
Rename the GlobalCount method to Count and rename the original Count method to CountUser in LoginLogStore.
Add a float64 case for bunit, sort of.
Theme.RunTmpl now returns ErrBadDefaultTemplate instead of panicking when an interpreted template doesn't exist.
Widget.Allowed now checks the zoneid.
Fire the alert off in the background in AddActivityAndNotifyTarget instead of blocking the request.
Use ErrBadDefaultTemplate instead of calling DefaultTemplates.Lookup directly for custom pages.
Split the page struct for the debug page into multiple structs to make things more organised.

Add the Count method to ProfileReplyStore.
Add the Count method to ReplyStore.
Add the DirSize utility function.

Add a few ActivityStream tests.

Secret gallery stuff.
This commit is contained in:
Azareal 2019-06-01 22:31:48 +10:00
parent 3f6966d541
commit 05c2ac3ce4
35 changed files with 451 additions and 206 deletions

52
common/activity_stream.go Normal file
View File

@ -0,0 +1,52 @@
package common
import "database/sql"
import "github.com/Azareal/Gosora/query_gen"
var Activity ActivityStream
type ActivityStream interface {
Add(alert Alert) (int, error)
Get(id int) (Alert, error)
Count() (count int)
}
type DefaultActivityStream struct {
add *sql.Stmt
get *sql.Stmt
count *sql.Stmt
}
func NewDefaultActivityStream(acc *qgen.Accumulator) (*DefaultActivityStream, error) {
return &DefaultActivityStream{
add: acc.Insert("activity_stream").Columns("actor, targetUser, event, elementType, elementID, createdAt").Fields("?,?,?,?,?,UTC_TIMESTAMP()").Prepare(),
get: acc.Select("activity_stream").Columns("actor, targetUser, event, elementType, elementID, createdAt").Where("asid = ?").Prepare(),
count: acc.Count("activity_stream").Prepare(),
}, acc.FirstError()
}
func (s *DefaultActivityStream) Add(alert Alert) (int, error) {
res, err := s.add.Exec(alert.ActorID, alert.TargetUserID, alert.Event, alert.ElementType, alert.ElementID)
if err != nil {
return 0, err
}
lastID, err := res.LastInsertId()
return int(lastID), err
}
func (s *DefaultActivityStream) Get(id int) (Alert, error) {
var a = Alert{ASID: id}
err := s.get.QueryRow(id).Scan(&a.ActorID, &a.TargetUserID, &a.Event, &a.ElementType, &a.ElementID, &a.CreatedAt)
return a, err
}
// TODO: Write a test for this
// Count returns the total number of activity stream items
func (s *DefaultActivityStream) Count() (count int) {
err := s.count.QueryRow().Scan(&count)
if err != nil {
LogError(err)
}
return count
}

View File

@ -12,6 +12,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
//"fmt"
"github.com/Azareal/Gosora/common/phrases" "github.com/Azareal/Gosora/common/phrases"
"github.com/Azareal/Gosora/query_gen" "github.com/Azareal/Gosora/query_gen"
@ -30,11 +31,9 @@ type Alert struct {
} }
type AlertStmts struct { type AlertStmts struct {
addActivity *sql.Stmt
notifyWatchers *sql.Stmt notifyWatchers *sql.Stmt
notifyOne *sql.Stmt notifyOne *sql.Stmt
getWatchers *sql.Stmt getWatchers *sql.Stmt
getActivityEntry *sql.Stmt
} }
var alertStmts AlertStmts var alertStmts AlertStmts
@ -44,14 +43,12 @@ var alertStmts AlertStmts
func init() { func init() {
DbInits.Add(func(acc *qgen.Accumulator) error { DbInits.Add(func(acc *qgen.Accumulator) error {
alertStmts = AlertStmts{ alertStmts = AlertStmts{
addActivity: acc.Insert("activity_stream").Columns("actor, targetUser, event, elementType, elementID, createdAt").Fields("?,?,?,?,?,UTC_TIMESTAMP()").Prepare(),
notifyWatchers: acc.SimpleInsertInnerJoin( notifyWatchers: acc.SimpleInsertInnerJoin(
qgen.DBInsert{"activity_stream_matches", "watcher, asid", ""}, qgen.DBInsert{"activity_stream_matches", "watcher, asid", ""},
qgen.DBJoin{"activity_stream", "activity_subscriptions", "activity_subscriptions.user, activity_stream.asid", "activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor", "asid = ?", "", ""}, qgen.DBJoin{"activity_stream", "activity_subscriptions", "activity_subscriptions.user, activity_stream.asid", "activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor", "asid = ?", "", ""},
), ),
notifyOne: acc.Insert("activity_stream_matches").Columns("watcher, asid").Fields("?,?").Prepare(), notifyOne: acc.Insert("activity_stream_matches").Columns("watcher, asid").Fields("?,?").Prepare(),
getWatchers: acc.SimpleInnerJoin("activity_stream", "activity_subscriptions", "activity_subscriptions.user", "activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor", "asid = ?", "", ""), getWatchers: acc.SimpleInnerJoin("activity_stream", "activity_subscriptions", "activity_subscriptions.user", "activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor", "asid = ?", "", ""),
getActivityEntry: acc.Select("activity_stream").Columns("actor, targetUser, event, elementType, elementID, createdAt").Where("asid = ?").Prepare(),
} }
return acc.FirstError() return acc.FirstError()
}) })
@ -79,7 +76,6 @@ func BuildAlert(alert Alert, user User /* The current user */) (out string, err
return return
} }
}*/ }*/
if alert.Event == "friend_invite" { if alert.Event == "friend_invite" {
return buildAlertString(".new_friend_invite", []string{alert.Actor.Name}, alert.Actor.Link, alert.Actor.Avatar, alert.ASID), nil return buildAlertString(".new_friend_invite", []string{alert.Actor.Name}, alert.Actor.Link, alert.Actor.Avatar, alert.ASID), nil
} }
@ -163,45 +159,41 @@ func buildAlertString(msg string, sub []string, path string, avatar string, asid
} }
func AddActivityAndNotifyAll(actor int, targetUser int, event string, elementType string, elementID int) error { func AddActivityAndNotifyAll(actor int, targetUser int, event string, elementType string, elementID int) error {
res, err := alertStmts.addActivity.Exec(actor, targetUser, event, elementType, elementID) id, err := Activity.Add(Alert{ActorID: actor, TargetUserID: targetUser, Event: event, ElementType: elementType, ElementID: elementID})
if err != nil { if err != nil {
return err return err
} }
lastID, err := res.LastInsertId() return NotifyWatchers(id)
if err != nil {
return err
}
return NotifyWatchers(lastID)
} }
func AddActivityAndNotifyTarget(alert Alert) error { func AddActivityAndNotifyTarget(alert Alert) error {
res, err := alertStmts.addActivity.Exec(alert.ActorID, alert.TargetUserID, alert.Event, alert.ElementType, alert.ElementID) id, err := Activity.Add(alert)
if err != nil { if err != nil {
return err return err
} }
lastID, err := res.LastInsertId() err = NotifyOne(alert.TargetUserID, id)
if err != nil { if err != nil {
return err return err
} }
alert.ASID = id
err = NotifyOne(alert.TargetUserID, lastID)
if err != nil {
return err
}
alert.ASID = int(lastID)
// Live alerts, if the target is online and WebSockets is enabled // Live alerts, if the target is online and WebSockets is enabled
if EnableWebsockets {
go func() {
_ = WsHub.pushAlert(alert.TargetUserID, alert) _ = WsHub.pushAlert(alert.TargetUserID, alert)
//fmt.Println("err:",err)
}()
}
return nil return nil
} }
func NotifyOne(watcher int, asid int64) error { func NotifyOne(watcher int, asid int) error {
_, err := alertStmts.notifyOne.Exec(watcher, asid) _, err := alertStmts.notifyOne.Exec(watcher, asid)
return err return err
} }
func NotifyWatchers(asid int64) error { func NotifyWatchers(asid int) error {
_, err := alertStmts.notifyWatchers.Exec(asid) _, err := alertStmts.notifyWatchers.Exec(asid)
if err != nil { if err != nil {
return err return err
@ -214,7 +206,7 @@ func NotifyWatchers(asid int64) error {
return nil return nil
} }
func notifyWatchers(asid int64) { func notifyWatchers(asid int) {
rows, err := alertStmts.getWatchers.Query(asid) rows, err := alertStmts.getWatchers.Query(asid)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
LogError(err) LogError(err)
@ -238,12 +230,10 @@ func notifyWatchers(asid int64) {
return return
} }
var alert = Alert{ASID: int(asid)} alert, err := Activity.Get(asid)
err = alertStmts.getActivityEntry.QueryRow(asid).Scan(&alert.ActorID, &alert.TargetUserID, &alert.Event, &alert.ElementType, &alert.ElementID, &alert.CreatedAt)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
LogError(err) LogError(err)
return return
} }
_ = WsHub.pushAlerts(uids, alert) _ = WsHub.pushAlerts(uids, alert)
} }

View File

@ -29,7 +29,7 @@ type AttachmentStore interface {
Add(sectionID int, sectionTable string, originID int, originTable string, uploadedBy int, path string, extra string) (int, error) Add(sectionID int, sectionTable string, originID int, originTable string, uploadedBy int, path string, extra string) (int, error)
MoveTo(sectionID int, originID int, originTable string) error MoveTo(sectionID int, originID int, originTable string) error
MoveToByExtra(sectionID int, originTable string, extra string) error MoveToByExtra(sectionID int, originTable string, extra string) error
GlobalCount() int Count() int
CountIn(originTable string, oid int) int CountIn(originTable string, oid int) int
CountInPath(path string) int CountInPath(path string) int
Delete(aid int) error Delete(aid int) error
@ -149,18 +149,18 @@ func (store *DefaultAttachmentStore) Add(sectionID int, sectionTable string, ori
return int(lid), err return int(lid), err
} }
func (store *DefaultAttachmentStore) MoveTo(sectionID int, originID int, originTable string) error { func (s *DefaultAttachmentStore) MoveTo(sectionID int, originID int, originTable string) error {
_, err := store.move.Exec(sectionID, originID, originTable) _, err := s.move.Exec(sectionID, originID, originTable)
return err return err
} }
func (store *DefaultAttachmentStore) MoveToByExtra(sectionID int, originTable string, extra string) error { func (s *DefaultAttachmentStore) MoveToByExtra(sectionID int, originTable string, extra string) error {
_, err := store.moveByExtra.Exec(sectionID, originTable, extra) _, err := s.moveByExtra.Exec(sectionID, originTable, extra)
return err return err
} }
func (store *DefaultAttachmentStore) GlobalCount() (count int) { func (s *DefaultAttachmentStore) Count() (count int) {
err := store.count.QueryRow().Scan(&count) err := s.count.QueryRow().Scan(&count)
if err != nil { if err != nil {
LogError(err) LogError(err)
} }

View File

@ -21,7 +21,7 @@ type LogItem struct {
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, ipaddress string, actorID int) (err error)
GlobalCount() int Count() int
GetOffset(offset int, perPage int) (logs []LogItem, err error) GetOffset(offset int, perPage int) (logs []LogItem, err error)
} }
@ -40,17 +40,17 @@ func NewModLogStore(acc *qgen.Accumulator) (*SQLModLogStore, error) {
} }
// TODO: Make a store for this? // TODO: Make a store for this?
func (store *SQLModLogStore) Create(action string, elementID int, elementType string, ipaddress string, actorID int) (err error) { func (s *SQLModLogStore) Create(action string, elementID int, elementType string, ipaddress string, actorID int) (err error) {
_, err = store.create.Exec(action, elementID, elementType, ipaddress, actorID) _, err = s.create.Exec(action, elementID, elementType, ipaddress, actorID)
return err return err
} }
func (store *SQLModLogStore) GlobalCount() (logCount int) { func (s *SQLModLogStore) Count() (count int) {
err := store.count.QueryRow().Scan(&logCount) err := s.count.QueryRow().Scan(&count)
if err != nil { if err != nil {
LogError(err) LogError(err)
} }
return logCount return count
} }
func buildLogList(rows *sql.Rows) (logs []LogItem, err error) { func buildLogList(rows *sql.Rows) (logs []LogItem, err error) {
@ -91,21 +91,21 @@ func NewAdminLogStore(acc *qgen.Accumulator) (*SQLAdminLogStore, error) {
} }
// TODO: Make a store for this? // TODO: Make a store for this?
func (store *SQLAdminLogStore) Create(action string, elementID int, elementType string, ipaddress string, actorID int) (err error) { func (s *SQLAdminLogStore) Create(action string, elementID int, elementType string, ipaddress string, actorID int) (err error) {
_, err = store.create.Exec(action, elementID, elementType, ipaddress, actorID) _, err = s.create.Exec(action, elementID, elementType, ipaddress, actorID)
return err return err
} }
func (store *SQLAdminLogStore) GlobalCount() (logCount int) { func (s *SQLAdminLogStore) Count() (count int) {
err := store.count.QueryRow().Scan(&logCount) err := s.count.QueryRow().Scan(&count)
if err != nil { if err != nil {
LogError(err) LogError(err)
} }
return logCount return count
} }
func (store *SQLAdminLogStore) GetOffset(offset int, perPage int) (logs []LogItem, err error) { func (s *SQLAdminLogStore) 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
} }

View File

@ -14,7 +14,7 @@ type DataStore interface {
DirtyGet(id int) interface{} DirtyGet(id int) interface{}
Get(id int) (interface{}, error) Get(id int) (interface{}, error)
BypassGet(id int) (interface{}, error) BypassGet(id int) (interface{}, error)
//GlobalCount() //Count() int
} }
// nolint // nolint

View File

@ -47,7 +47,7 @@ type ForumStore interface {
Create(forumName string, forumDesc string, active bool, preset string) (int, error) Create(forumName string, forumDesc string, active bool, preset string) (int, error)
UpdateOrder(updateMap map[int]int) error UpdateOrder(updateMap map[int]int) error
GlobalCount() int Count() int
} }
type ForumCache interface { type ForumCache interface {
@ -379,13 +379,13 @@ func (s *MemoryForumStore) Length() (length int) {
} }
// TODO: Get the total count of forums in the forum store rather than doing a heavy query for this? // TODO: Get the total count of forums in the forum store rather than doing a heavy query for this?
// GlobalCount returns the total number of forums // Count returns the total number of forums
func (s *MemoryForumStore) GlobalCount() (fcount int) { func (s *MemoryForumStore) Count() (count int) {
err := s.count.QueryRow().Scan(&fcount) err := s.count.QueryRow().Scan(&count)
if err != nil { if err != nil {
LogError(err) LogError(err)
} }
return fcount return count
} }
// TODO: Work on SqlForumStore // TODO: Work on SqlForumStore

View File

@ -25,7 +25,7 @@ type GroupStore interface {
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
GlobalCount() int Count() int
} }
type GroupCache interface { type GroupCache interface {
@ -336,14 +336,14 @@ func (mgs *MemoryGroupStore) GetRange(lower int, higher int) (groups []*Group, e
return groups, nil return groups, nil
} }
func (mgs *MemoryGroupStore) Length() int { func (s *MemoryGroupStore) Length() int {
mgs.RLock() s.RLock()
defer mgs.RUnlock() defer s.RUnlock()
return mgs.groupCount return s.groupCount
} }
func (mgs *MemoryGroupStore) GlobalCount() (count int) { func (s *MemoryGroupStore) Count() (count int) {
err := mgs.count.QueryRow().Scan(&count) err := s.count.QueryRow().Scan(&count)
if err != nil { if err != nil {
LogError(err) LogError(err)
} }

View File

@ -55,7 +55,7 @@ func (log *RegLogItem) Create() (id int, err error) {
} }
type RegLogStore interface { type RegLogStore interface {
GlobalCount() (logCount int) Count() (count int)
GetOffset(offset int, perPage int) (logs []RegLogItem, err error) GetOffset(offset int, perPage int) (logs []RegLogItem, err error)
} }
@ -71,12 +71,12 @@ func NewRegLogStore(acc *qgen.Accumulator) (*SQLRegLogStore, error) {
}, acc.FirstError() }, acc.FirstError()
} }
func (store *SQLRegLogStore) GlobalCount() (logCount int) { func (s *SQLRegLogStore) Count() (count int) {
err := store.count.QueryRow().Scan(&logCount) err := s.count.QueryRow().Scan(&count)
if err != nil { if err != nil {
LogError(err) LogError(err)
} }
return logCount return count
} }
func (store *SQLRegLogStore) GetOffset(offset int, perPage int) (logs []RegLogItem, err error) { func (store *SQLRegLogStore) GetOffset(offset int, perPage int) (logs []RegLogItem, err error) {
@ -142,8 +142,8 @@ func (log *LoginLogItem) Create() (id int, err error) {
} }
type LoginLogStore interface { type LoginLogStore interface {
GlobalCount() (logCount int) Count() (count int)
Count(uid int) (logCount int) CountUser(uid int) (count int)
GetOffset(uid int, offset int, perPage int) (logs []LoginLogItem, err error) GetOffset(uid int, offset int, perPage int) (logs []LoginLogItem, err error)
} }
@ -161,20 +161,20 @@ func NewLoginLogStore(acc *qgen.Accumulator) (*SQLLoginLogStore, error) {
}, acc.FirstError() }, acc.FirstError()
} }
func (store *SQLLoginLogStore) GlobalCount() (logCount int) { func (s *SQLLoginLogStore) Count() (count int) {
err := store.count.QueryRow().Scan(&logCount) err := s.count.QueryRow().Scan(&count)
if err != nil { if err != nil {
LogError(err) LogError(err)
} }
return logCount return count
} }
func (store *SQLLoginLogStore) Count(uid int) (logCount int) { func (s *SQLLoginLogStore) CountUser(uid int) (count int) {
err := store.countForUser.QueryRow(uid).Scan(&logCount) err := s.countForUser.QueryRow(uid).Scan(&count)
if err != nil { if err != nil {
LogError(err) LogError(err)
} }
return logCount return count
} }
func (store *SQLLoginLogStore) GetOffset(uid int, offset int, perPage int) (logs []LoginLogItem, err error) { func (store *SQLLoginLogStore) GetOffset(uid int, offset int, perPage int) (logs []LoginLogItem, err error) {

View File

@ -72,7 +72,7 @@ var Pages PageStore
// Holds the custom pages, but doesn't include the template pages in /pages/ which are a lot more flexible yet harder to use and which are too risky security-wise to make editable in the Control Panel // Holds the custom pages, but doesn't include the template pages in /pages/ which are a lot more flexible yet harder to use and which are too risky security-wise to make editable in the Control Panel
type PageStore interface { type PageStore interface {
GlobalCount() (pageCount int) Count() (count int)
Get(id int) (*CustomPage, error) Get(id int) (*CustomPage, error)
GetByName(name string) (*CustomPage, error) GetByName(name string) (*CustomPage, error)
GetOffset(offset int, perPage int) (pages []*CustomPage, err error) GetOffset(offset int, perPage int) (pages []*CustomPage, err error)
@ -99,15 +99,15 @@ func NewDefaultPageStore(acc *qgen.Accumulator) (*DefaultPageStore, error) {
}, acc.FirstError() }, acc.FirstError()
} }
func (store *DefaultPageStore) GlobalCount() (pageCount int) { func (s *DefaultPageStore) Count() (count int) {
err := store.count.QueryRow().Scan(&pageCount) err := s.count.QueryRow().Scan(&count)
if err != nil { if err != nil {
LogError(err) LogError(err)
} }
return pageCount return count
} }
func (store *DefaultPageStore) parseAllowedGroups(raw string, page *CustomPage) error { func (s *DefaultPageStore) parseAllowedGroups(raw string, page *CustomPage) error {
if raw == "" { if raw == "" {
return nil return nil
} }

View File

@ -594,6 +594,34 @@ type PanelRegLogsPage struct {
Paginator Paginator
} }
type DebugPageCache struct {
Topics int
Users int
Replies int
TCap int
UCap int
RCap int
TopicListThaw bool
}
type DebugPageDatabase struct {
Topics int
Users int
Replies int
ProfileReplies int
ActivityStream int
}
type DebugPageDisk struct {
Static int
Attachments int
Avatars int
Logs int
Backups int
}
type PanelDebugPage struct { type PanelDebugPage struct {
*BasePanelPage *BasePanelPage
GoVersion string GoVersion string
@ -607,15 +635,9 @@ type PanelDebugPage struct {
CPUs int CPUs int
MemStats runtime.MemStats MemStats runtime.MemStats
TCache int Cache DebugPageCache
UCache int Database DebugPageDatabase
RCache int Disk DebugPageDisk
TCap int
UCap int
RCap int
TopicListThaw bool
} }
type PageSimple struct { type PageSimple struct {

View File

@ -51,7 +51,7 @@ type PollStore interface {
Create(parent Pollable, pollType int, pollOptions map[int]string) (int, error) Create(parent Pollable, pollType int, pollOptions map[int]string) (int, error)
CastVote(optionIndex int, pollID int, uid int, ipaddress string) error CastVote(optionIndex int, pollID int, uid int, ipaddress string) error
Reload(id int) error Reload(id int) error
//GlobalCount() int //Count() int
SetCache(cache PollCache) SetCache(cache PollCache)
GetCache() PollCache GetCache() PollCache
@ -68,7 +68,7 @@ type DefaultPollStore struct {
incrementVoteCount *sql.Stmt incrementVoteCount *sql.Stmt
incrementVoteCountForOption *sql.Stmt incrementVoteCountForOption *sql.Stmt
delete *sql.Stmt delete *sql.Stmt
//pollCount *sql.Stmt //count *sql.Stmt
} }
func NewDefaultPollStore(cache PollCache) (*DefaultPollStore, error) { func NewDefaultPollStore(cache PollCache) (*DefaultPollStore, error) {
@ -86,7 +86,7 @@ func NewDefaultPollStore(cache PollCache) (*DefaultPollStore, error) {
addVote: acc.Insert("polls_votes").Columns("pollID, uid, option, castAt, ipaddress").Fields("?,?,?,UTC_TIMESTAMP(),?").Prepare(), addVote: acc.Insert("polls_votes").Columns("pollID, uid, option, castAt, ipaddress").Fields("?,?,?,UTC_TIMESTAMP(),?").Prepare(),
incrementVoteCount: acc.Update("polls").Set("votes = votes + 1").Where("pollID = ?").Prepare(), incrementVoteCount: acc.Update("polls").Set("votes = votes + 1").Where("pollID = ?").Prepare(),
incrementVoteCountForOption: acc.Update("polls_options").Set("votes = votes + 1").Where("option = ? AND pollID = ?").Prepare(), incrementVoteCountForOption: acc.Update("polls_options").Set("votes = votes + 1").Where("option = ? AND pollID = ?").Prepare(),
//pollCount: acc.SimpleCount("polls", "", ""), //count: acc.SimpleCount("polls", "", ""),
}, acc.FirstError() }, acc.FirstError()
} }

View File

@ -3,7 +3,7 @@ package common
import ( import (
"database/sql" "database/sql"
"github.com/Azareal/Gosora/query_gen" qgen "github.com/Azareal/Gosora/query_gen"
) )
var Prstore ProfileReplyStore var Prstore ProfileReplyStore
@ -11,6 +11,7 @@ var Prstore ProfileReplyStore
type ProfileReplyStore interface { type ProfileReplyStore interface {
Get(id int) (*ProfileReply, error) Get(id int) (*ProfileReply, error)
Create(profileID int, content string, createdBy int, ipaddress string) (id int, err error) Create(profileID int, content string, createdBy int, ipaddress string) (id int, err error)
Count() (count int)
} }
// TODO: Refactor this to stop using the global stmt store // TODO: Refactor this to stop using the global stmt store
@ -18,23 +19,25 @@ type ProfileReplyStore interface {
type SQLProfileReplyStore struct { type SQLProfileReplyStore struct {
get *sql.Stmt get *sql.Stmt
create *sql.Stmt create *sql.Stmt
count *sql.Stmt
} }
func NewSQLProfileReplyStore(acc *qgen.Accumulator) (*SQLProfileReplyStore, error) { func NewSQLProfileReplyStore(acc *qgen.Accumulator) (*SQLProfileReplyStore, error) {
return &SQLProfileReplyStore{ return &SQLProfileReplyStore{
get: acc.Select("users_replies").Columns("uid, content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress").Where("rid = ?").Prepare(), get: acc.Select("users_replies").Columns("uid, content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress").Where("rid = ?").Prepare(),
create: acc.Insert("users_replies").Columns("uid, content, parsed_content, createdAt, createdBy, ipaddress").Fields("?,?,?,UTC_TIMESTAMP(),?,?").Prepare(), create: acc.Insert("users_replies").Columns("uid, content, parsed_content, createdAt, createdBy, ipaddress").Fields("?,?,?,UTC_TIMESTAMP(),?,?").Prepare(),
count: acc.Count("users_replies").Prepare(),
}, acc.FirstError() }, acc.FirstError()
} }
func (store *SQLProfileReplyStore) Get(id int) (*ProfileReply, error) { func (s *SQLProfileReplyStore) Get(id int) (*ProfileReply, error) {
reply := ProfileReply{ID: id} r := ProfileReply{ID: id}
err := store.get.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IPAddress) err := s.get.QueryRow(id).Scan(&r.ParentID, &r.Content, &r.CreatedBy, &r.CreatedAt, &r.LastEdit, &r.LastEditBy, &r.IPAddress)
return &reply, err return &r, err
} }
func (store *SQLProfileReplyStore) Create(profileID int, content string, createdBy int, ipaddress string) (id int, err error) { func (s *SQLProfileReplyStore) Create(profileID int, content string, createdBy int, ipaddress string) (id int, err error) {
res, err := store.create.Exec(profileID, content, ParseMessage(content, 0, ""), createdBy, ipaddress) res, err := s.create.Exec(profileID, content, ParseMessage(content, 0, ""), createdBy, ipaddress)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -42,7 +45,16 @@ func (store *SQLProfileReplyStore) Create(profileID int, content string, created
if err != nil { if err != nil {
return 0, err return 0, err
} }
// Should we reload the user? // Should we reload the user?
return int(lastID), err return int(lastID), err
} }
// TODO: Write a test for this
// Count returns the total number of topic replies on these forums
func (s *SQLProfileReplyStore) Count() (count int) {
err := s.count.QueryRow().Scan(&count)
if err != nil {
LogError(err)
}
return count
}

View File

@ -9,6 +9,7 @@ var Rstore ReplyStore
type ReplyStore interface { type ReplyStore interface {
Get(id int) (*Reply, error) Get(id int) (*Reply, error)
Create(topic *Topic, content string, ipaddress string, uid int) (id int, err error) Create(topic *Topic, content string, ipaddress string, uid int) (id int, err error)
Count() (count int)
SetCache(cache ReplyCache) SetCache(cache ReplyCache)
GetCache() ReplyCache GetCache() ReplyCache
@ -19,6 +20,7 @@ type SQLReplyStore struct {
get *sql.Stmt get *sql.Stmt
create *sql.Stmt create *sql.Stmt
count *sql.Stmt
} }
func NewSQLReplyStore(acc *qgen.Accumulator, cache ReplyCache) (*SQLReplyStore, error) { func NewSQLReplyStore(acc *qgen.Accumulator, cache ReplyCache) (*SQLReplyStore, error) {
@ -29,22 +31,22 @@ func NewSQLReplyStore(acc *qgen.Accumulator, cache ReplyCache) (*SQLReplyStore,
cache: cache, cache: cache,
get: acc.Select("replies").Columns("tid, content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress, likeCount, attachCount, actionType").Where("rid = ?").Prepare(), get: acc.Select("replies").Columns("tid, content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress, likeCount, attachCount, actionType").Where("rid = ?").Prepare(),
create: acc.Insert("replies").Columns("tid, content, parsed_content, createdAt, lastUpdated, ipaddress, words, createdBy").Fields("?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?").Prepare(), create: acc.Insert("replies").Columns("tid, content, parsed_content, createdAt, lastUpdated, ipaddress, words, createdBy").Fields("?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?").Prepare(),
count: acc.Count("replies").Prepare(),
}, acc.FirstError() }, acc.FirstError()
} }
func (s *SQLReplyStore) Get(id int) (*Reply, error) { func (s *SQLReplyStore) Get(id int) (*Reply, error) {
//log.Print("SQLReplyStore.Get") r, err := s.cache.Get(id)
reply, err := s.cache.Get(id)
if err == nil { if err == nil {
return reply, nil return r, nil
} }
reply = &Reply{ID: id} r = &Reply{ID: id}
err = s.get.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IPAddress, &reply.LikeCount, &reply.AttachCount, &reply.ActionType) 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)
if err == nil { if err == nil {
_ = s.cache.Set(reply) _ = s.cache.Set(r)
} }
return reply, err return r, err
} }
// TODO: Write a test for this // TODO: Write a test for this
@ -62,6 +64,16 @@ func (s *SQLReplyStore) Create(topic *Topic, content string, ipaddress string, u
return int(lastID), topic.AddReply(int(lastID), uid) return int(lastID), topic.AddReply(int(lastID), uid)
} }
// TODO: Write a test for this
// Count returns the total number of topic replies on these forums
func (s *SQLReplyStore) Count() (count int) {
err := s.count.QueryRow().Scan(&count)
if err != nil {
LogError(err)
}
return count
}
func (s *SQLReplyStore) SetCache(cache ReplyCache) { func (s *SQLReplyStore) SetCache(cache ReplyCache) {
s.cache = cache s.cache = cache
} }

View File

@ -138,10 +138,10 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header
//h.Set("Content-Security-Policy", "default-src 'self'") //h.Set("Content-Security-Policy", "default-src 'self'")
// TODO: GDPR. Add a global control panel notice warning the admins of staff members who don't have 2FA enabled // TODO: GDPR. Add a global control panel notice warning the admins of staff members who don't have 2FA enabled
stats.Users = Users.GlobalCount() stats.Users = Users.Count()
stats.Groups = Groups.GlobalCount() stats.Groups = Groups.Count()
stats.Forums = Forums.GlobalCount() stats.Forums = Forums.Count()
stats.Pages = Pages.GlobalCount() stats.Pages = Pages.Count()
stats.Settings = len(header.Settings) stats.Settings = len(header.Settings)
stats.WordFilters = WordFilters.EstCount() stats.WordFilters = WordFilters.EstCount()
stats.Themes = len(Themes) stats.Themes = len(Themes)

View File

@ -745,6 +745,8 @@ func initDefaultTmplFuncMap() {
byteFloat, unit = ConvertByteUnit(float64(bytes)) byteFloat, unit = ConvertByteUnit(float64(bytes))
case uint64: case uint64:
byteFloat, unit = ConvertByteUnit(float64(bytes)) byteFloat, unit = ConvertByteUnit(float64(bytes))
case float64:
byteFloat, unit = ConvertByteUnit(bytes)
default: default:
panic("bytes is not an int, int64 or uint64") panic("bytes is not an int, int64 or uint64")
} }

View File

@ -23,6 +23,7 @@ import (
) )
var ErrNoDefaultTheme = errors.New("The default theme isn't registered in the system") var ErrNoDefaultTheme = errors.New("The default theme isn't registered in the system")
var ErrBadDefaultTemplate = errors.New("The template you tried to load doesn't exist in the interpreted pool.")
type Theme struct { type Theme struct {
Path string // Redirect this file to another folder Path string // Redirect this file to another folder
@ -312,6 +313,9 @@ func (theme *Theme) RunTmpl(template string, pi interface{}, w io.Writer) error
if !ok { if !ok {
mapping = template mapping = template
} }
if theme.IntTmplHandle.Lookup(mapping+".html") == nil {
return ErrBadDefaultTemplate
}
return theme.IntTmplHandle.ExecuteTemplate(w, mapping+".html", pi) return theme.IntTmplHandle.ExecuteTemplate(w, mapping+".html", pi)
default: default:
log.Print("theme ", theme) log.Print("theme ", theme)

View File

@ -36,7 +36,7 @@ type TopicStore interface {
// TODO: Implement these two methods // TODO: Implement these two methods
//Replies(tid int) ([]*Reply, error) //Replies(tid int) ([]*Reply, error)
//RepliesRange(tid int, lower int, higher int) ([]*Reply, error) //RepliesRange(tid int, lower int, higher int) ([]*Reply, error)
GlobalCount() int Count() int
SetCache(cache TopicCache) SetCache(cache TopicCache)
GetCache() TopicCache GetCache() TopicCache
@ -47,7 +47,7 @@ type DefaultTopicStore struct {
get *sql.Stmt get *sql.Stmt
exists *sql.Stmt exists *sql.Stmt
topicCount *sql.Stmt count *sql.Stmt
create *sql.Stmt create *sql.Stmt
} }
@ -61,7 +61,7 @@ func NewDefaultTopicStore(cache TopicCache) (*DefaultTopicStore, error) {
cache: cache, cache: cache,
get: acc.Select("topics").Columns("title, content, createdBy, createdAt, lastReplyBy, lastReplyAt, lastReplyID, is_closed, sticky, parentID, ipaddress, views, postCount, likeCount, attachCount, poll, data").Where("tid = ?").Prepare(), get: acc.Select("topics").Columns("title, content, createdBy, createdAt, lastReplyBy, lastReplyAt, lastReplyID, is_closed, sticky, parentID, ipaddress, views, postCount, likeCount, attachCount, poll, data").Where("tid = ?").Prepare(),
exists: acc.Select("topics").Columns("tid").Where("tid = ?").Prepare(), exists: acc.Select("topics").Columns("tid").Where("tid = ?").Prepare(),
topicCount: acc.Count("topics").Prepare(), count: acc.Count("topics").Prepare(),
create: acc.Insert("topics").Columns("parentID, title, content, parsed_content, createdAt, lastReplyAt, lastReplyBy, ipaddress, words, createdBy").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,?").Prepare(), create: acc.Insert("topics").Columns("parentID, title, content, parsed_content, createdAt, lastReplyAt, lastReplyBy, ipaddress, words, createdBy").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,?").Prepare(),
}, acc.FirstError() }, acc.FirstError()
} }
@ -239,13 +239,13 @@ func (mts *DefaultTopicStore) AddLastTopic(item *Topic, fid int) error {
return nil return nil
} }
// GlobalCount returns the total number of topics on these forums // Count returns the total number of topics on these forums
func (mts *DefaultTopicStore) GlobalCount() (tcount int) { func (s *DefaultTopicStore) Count() (count int) {
err := mts.topicCount.QueryRow().Scan(&tcount) err := s.count.QueryRow().Scan(&count)
if err != nil { if err != nil {
LogError(err) LogError(err)
} }
return tcount return count
} }
func (mts *DefaultTopicStore) SetCache(cache TopicCache) { func (mts *DefaultTopicStore) SetCache(cache TopicCache) {

View File

@ -26,7 +26,7 @@ type UserStore interface {
BypassGet(id int) (*User, error) BypassGet(id int) (*User, error)
Create(username string, password string, email string, group int, active bool) (int, error) Create(username string, password string, email string, group int, active bool) (int, error)
Reload(id int) error Reload(id int) error
GlobalCount() int Count() int
SetCache(cache UserCache) SetCache(cache UserCache)
GetCache() UserCache GetCache() UserCache
@ -41,7 +41,7 @@ type DefaultUserStore struct {
exists *sql.Stmt exists *sql.Stmt
register *sql.Stmt register *sql.Stmt
usernameExists *sql.Stmt usernameExists *sql.Stmt
userCount *sql.Stmt count *sql.Stmt
} }
// NewDefaultUserStore gives you a new instance of DefaultUserStore // NewDefaultUserStore gives you a new instance of DefaultUserStore
@ -59,7 +59,7 @@ func NewDefaultUserStore(cache UserCache) (*DefaultUserStore, error) {
exists: acc.SimpleSelect("users", "uid", "uid = ?", "", ""), exists: acc.SimpleSelect("users", "uid", "uid = ?", "", ""),
register: acc.Insert("users").Columns("name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt, lastLiked, oldestItemLikedCreatedAt").Fields("?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(), // TODO: Implement user_count on users_groups here register: acc.Insert("users").Columns("name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt, lastLiked, oldestItemLikedCreatedAt").Fields("?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(), // TODO: Implement user_count on users_groups here
usernameExists: acc.SimpleSelect("users", "name", "name = ?", "", ""), usernameExists: acc.SimpleSelect("users", "name", "name = ?", "", ""),
userCount: acc.Count("users").Prepare(), count: acc.Count("users").Prepare(),
}, acc.FirstError() }, acc.FirstError()
} }
@ -282,24 +282,24 @@ func (mus *DefaultUserStore) Create(username string, password string, email stri
return int(lastID), err return int(lastID), err
} }
// GlobalCount returns the total number of users registered on the forums // Count returns the total number of users registered on the forums
func (mus *DefaultUserStore) GlobalCount() (ucount int) { func (s *DefaultUserStore) Count() (count int) {
err := mus.userCount.QueryRow().Scan(&ucount) err := s.count.QueryRow().Scan(&count)
if err != nil { if err != nil {
LogError(err) LogError(err)
} }
return ucount return count
} }
func (mus *DefaultUserStore) SetCache(cache UserCache) { func (s *DefaultUserStore) SetCache(cache UserCache) {
mus.cache = cache s.cache = cache
} }
// TODO: We're temporarily doing this so that you can do ucache != nil in getTopicUser. Refactor it. // TODO: We're temporarily doing this so that you can do ucache != nil in getTopicUser. Refactor it.
func (mus *DefaultUserStore) GetCache() UserCache { func (s *DefaultUserStore) GetCache() UserCache {
_, ok := mus.cache.(*NullUserCache) _, ok := s.cache.(*NullUserCache)
if ok { if ok {
return nil return nil
} }
return mus.cache return s.cache
} }

View File

@ -10,6 +10,7 @@ import (
"crypto/rand" "crypto/rand"
"encoding/base32" "encoding/base32"
"encoding/base64" "encoding/base64"
"path/filepath"
"errors" "errors"
"fmt" "fmt"
"html" "html"
@ -509,3 +510,17 @@ func BuildSlug(slug string, id int) string {
} }
return slug + "." + strconv.Itoa(id) return slug + "." + strconv.Itoa(id)
} }
func DirSize(path string) (int, error) {
var size int64
err := filepath.Walk(path, func(_ string, file os.FileInfo, err error) error {
if err != nil {
return err
}
if !file.IsDir() {
size += file.Size()
}
return err
})
return int(size), err
}

View File

@ -4,6 +4,7 @@ import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"strings" "strings"
"strconv"
"sync/atomic" "sync/atomic"
"github.com/Azareal/Gosora/query_gen" "github.com/Azareal/Gosora/query_gen"
@ -76,11 +77,22 @@ func (widget *Widget) Copy() (owidget *Widget) {
// TODO: Test this // TODO: Test this
// TODO: Add support for zone:id. Perhaps, carry a ZoneID property around in *Header? It might allow some weirdness like frontend[5] which matches any zone with an ID of 5 but it would be a tad faster than verifying each zone, although it might be problematic if users end up relying on this behaviour for areas which don't pass IDs to the widgets system but *probably* should // TODO: Add support for zone:id. Perhaps, carry a ZoneID property around in *Header? It might allow some weirdness like frontend[5] which matches any zone with an ID of 5 but it would be a tad faster than verifying each zone, although it might be problematic if users end up relying on this behaviour for areas which don't pass IDs to the widgets system but *probably* should
func (widget *Widget) Allowed(zone string) bool { // TODO: Add a selector which also matches topics inside a specific forum?
func (widget *Widget) Allowed(zone string, zoneid int) bool {
for _, loc := range strings.Split(widget.Location, "|") { for _, loc := range strings.Split(widget.Location, "|") {
if len(loc) == 0 {
continue
}
sloc := strings.Split(":",loc)
if len(sloc) > 1 {
iloc, _ := strconv.Atoi(sloc[1])
if zoneid != 0 && iloc != zoneid {
continue
}
}
if loc == "global" || loc == zone { if loc == "global" || loc == zone {
return true return true
} else if len(loc) > 0 && loc[0] == '!' { } else if loc[0] == '!' {
loc = loc[1:] loc = loc[1:]
if loc != "global" && loc != zone { if loc != "global" && loc != zone {
return true return true

View File

@ -166,7 +166,7 @@ func HasWidgets(dock string, header *Header) bool {
if !widget.Enabled { if !widget.Enabled {
continue continue
} }
if widget.Allowed(header.Zone) { if widget.Allowed(header.Zone,header.ZoneID) {
wcount++ wcount++
} }
} }
@ -210,7 +210,7 @@ func BuildWidget(dock string, header *Header) (sbody string) {
if !widget.Enabled { if !widget.Enabled {
continue continue
} }
if widget.Allowed(header.Zone) { if widget.Allowed(header.Zone,header.ZoneID) {
item, err := widget.Build(header) item, err := widget.Build(header)
if err != nil { if err != nil {
LogError(err) LogError(err)

View File

@ -24,7 +24,7 @@ type WordFilterStore interface {
Update(id int, find string, replacement string) error Update(id int, find string, replacement string) error
Length() int Length() int
EstCount() int EstCount() int
GlobalCount() (count int) Count() (count int)
} }
type DefaultWordFilterStore struct { type DefaultWordFilterStore struct {
@ -53,9 +53,9 @@ func NewDefaultWordFilterStore(acc *qgen.Accumulator) (*DefaultWordFilterStore,
} }
// ReloadAll drops all the items in the memory cache and replaces them with fresh copies from the database // ReloadAll drops all the items in the memory cache and replaces them with fresh copies from the database
func (store *DefaultWordFilterStore) ReloadAll() error { func (s *DefaultWordFilterStore) ReloadAll() error {
var wordFilters = make(map[int]*WordFilter) var wordFilters = make(map[int]*WordFilter)
filters, err := store.bypassGetAll() filters, err := s.bypassGetAll()
if err != nil { if err != nil {
return err return err
} }
@ -64,13 +64,13 @@ func (store *DefaultWordFilterStore) ReloadAll() error {
wordFilters[filter.ID] = filter wordFilters[filter.ID] = filter
} }
store.box.Store(wordFilters) s.box.Store(wordFilters)
return nil return nil
} }
// ? - Return pointers to word filters intead to save memory? -- A map is a pointer. // ? - Return pointers to word filters intead to save memory? -- A map is a pointer.
func (store *DefaultWordFilterStore) bypassGetAll() (filters []*WordFilter, err error) { func (s *DefaultWordFilterStore) bypassGetAll() (filters []*WordFilter, err error) {
rows, err := store.getAll.Query() rows, err := s.getAll.Query()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -88,49 +88,49 @@ func (store *DefaultWordFilterStore) bypassGetAll() (filters []*WordFilter, err
} }
// GetAll returns all of the word filters in a map. Do note mutate this map (or maps returned from any store not explicitly noted as copies) as multiple threads may be accessing it at once // GetAll returns all of the word filters in a map. Do note mutate this map (or maps returned from any store not explicitly noted as copies) as multiple threads may be accessing it at once
func (store *DefaultWordFilterStore) GetAll() (filters map[int]*WordFilter, err error) { func (s *DefaultWordFilterStore) GetAll() (filters map[int]*WordFilter, err error) {
return store.box.Load().(map[int]*WordFilter), nil return s.box.Load().(map[int]*WordFilter), nil
} }
// Create adds a new word filter to the database and refreshes the memory cache // Create adds a new word filter to the database and refreshes the memory cache
func (store *DefaultWordFilterStore) Create(find string, replacement string) error { func (s *DefaultWordFilterStore) Create(find string, replacement string) error {
_, err := store.create.Exec(find, replacement) _, err := s.create.Exec(find, replacement)
if err != nil { if err != nil {
return err return err
} }
return store.ReloadAll() return s.ReloadAll()
} }
// Delete removes a word filter from the database and refreshes the memory cache // Delete removes a word filter from the database and refreshes the memory cache
func (store *DefaultWordFilterStore) Delete(id int) error { func (s *DefaultWordFilterStore) Delete(id int) error {
_, err := store.delete.Exec(id) _, err := s.delete.Exec(id)
if err != nil { if err != nil {
return err return err
} }
return store.ReloadAll() return s.ReloadAll()
} }
func (store *DefaultWordFilterStore) Update(id int, find string, replacement string) error { func (s *DefaultWordFilterStore) Update(id int, find string, replacement string) error {
_, err := store.update.Exec(find, replacement, id) _, err := s.update.Exec(find, replacement, id)
if err != nil { if err != nil {
return err return err
} }
return store.ReloadAll() return s.ReloadAll()
} }
// Length gets the number of word filters currently in memory, for the DefaultWordFilterStore, this should be all of them // Length gets the number of word filters currently in memory, for the DefaultWordFilterStore, this should be all of them
func (store *DefaultWordFilterStore) Length() int { func (s *DefaultWordFilterStore) Length() int {
return len(store.box.Load().(map[int]*WordFilter)) return len(s.box.Load().(map[int]*WordFilter))
} }
// EstCount provides the same result as Length(), intended for alternate implementations of WordFilterStore, so that Length is the number of items in cache, if only a subset is held there and EstCount is the total count // EstCount provides the same result as Length(), intended for alternate implementations of WordFilterStore, so that Length is the number of items in cache, if only a subset is held there and EstCount is the total count
func (store *DefaultWordFilterStore) EstCount() int { func (s *DefaultWordFilterStore) EstCount() int {
return len(store.box.Load().(map[int]*WordFilter)) return len(s.box.Load().(map[int]*WordFilter))
} }
// GlobalCount gets the total number of word filters directly from the database // Count gets the total number of word filters directly from the database
func (store *DefaultWordFilterStore) GlobalCount() (count int) { func (s *DefaultWordFilterStore) Count() (count int) {
err := store.count.QueryRow().Scan(&count) err := s.count.QueryRow().Scan(&count)
if err != nil { if err != nil {
LogError(err) LogError(err)
} }

View File

@ -243,6 +243,10 @@ func storeInit() (err error) {
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
c.Activity, err = c.NewDefaultActivityStream(acc)
if err != nil {
return errors.WithStack(err)
}
// TODO: Let the admin choose other thumbnailers, maybe ones defined in plugins // TODO: Let the admin choose other thumbnailers, maybe ones defined in plugins
c.Thumbnailer = c.NewCaireThumbnailer() c.Thumbnailer = c.NewCaireThumbnailer()
@ -486,7 +490,7 @@ func main() {
// TODO: Add a LastRequested field to cached User structs to avoid evicting the same things which wind up getting loaded again anyway? // TODO: Add a LastRequested field to cached User structs to avoid evicting the same things which wind up getting loaded again anyway?
if ucache != nil { if ucache != nil {
ucap := ucache.GetCapacity() ucap := ucache.GetCapacity()
if ucache.Length() <= ucap || c.Users.GlobalCount() <= ucap { if ucache.Length() <= ucap || c.Users.Count() <= ucap {
couldNotDealloc = false couldNotDealloc = false
continue continue
} }

View File

@ -164,7 +164,7 @@ func userStoreTest(t *testing.T, newUserID int) {
expect(t, !c.Users.Exists(newUserID), fmt.Sprintf("UID #%d shouldn't exist", newUserID)) expect(t, !c.Users.Exists(newUserID), fmt.Sprintf("UID #%d shouldn't exist", newUserID))
expect(t, isCacheLengthZero(ucache), fmt.Sprintf("User cache length should be 0, not %d", cacheLength(ucache))) expect(t, isCacheLengthZero(ucache), fmt.Sprintf("User cache length should be 0, not %d", cacheLength(ucache)))
expectIntToBeX(t, c.Users.GlobalCount(), 1, "The number of users should be one, not %d") expectIntToBeX(t, c.Users.Count(), 1, "The number of users should be one, not %d")
var awaitingActivation = 5 var awaitingActivation = 5
// TODO: Write tests for the registration validators // TODO: Write tests for the registration validators
@ -452,7 +452,7 @@ func topicStoreTest(t *testing.T, newID int) {
ok = c.Topics.Exists(1) ok = c.Topics.Exists(1)
expect(t, ok, "TID #1 should exist") expect(t, ok, "TID #1 should exist")
count := c.Topics.GlobalCount() count := c.Topics.Count()
expect(t, count == 1, fmt.Sprintf("Global count for topics should be 1, not %d", count)) expect(t, count == 1, fmt.Sprintf("Global count for topics should be 1, not %d", count))
//Create(fid int, topicName string, content string, uid int, ipaddress string) (tid int, err error) //Create(fid int, topicName string, content string, uid int, ipaddress string) (tid int, err error)
@ -461,7 +461,7 @@ func topicStoreTest(t *testing.T, newID int) {
expect(t, tid == newID, fmt.Sprintf("TID for the new topic should be %d, not %d", newID, tid)) expect(t, tid == newID, fmt.Sprintf("TID for the new topic should be %d, not %d", newID, tid))
expect(t, c.Topics.Exists(newID), fmt.Sprintf("TID #%d should exist", newID)) expect(t, c.Topics.Exists(newID), fmt.Sprintf("TID #%d should exist", newID))
count = c.Topics.GlobalCount() count = c.Topics.Count()
expect(t, count == 2, fmt.Sprintf("Global count for topics should be 2, not %d", count)) expect(t, count == 2, fmt.Sprintf("Global count for topics should be 2, not %d", count))
var iFrag = func(cond bool) string { var iFrag = func(cond bool) string {
@ -541,7 +541,7 @@ func TestForumStore(t *testing.T) {
fcache, ok := c.Forums.(c.ForumCache) fcache, ok := c.Forums.(c.ForumCache)
expect(t, ok, "Unable to cast ForumStore to ForumCache") expect(t, ok, "Unable to cast ForumStore to ForumCache")
expect(t, c.Forums.GlobalCount() == 2, "The forumstore global count should be 2") expect(t, c.Forums.Count() == 2, "The forumstore global count should be 2")
expect(t, fcache.Length() == 2, "The forum cache length should be 2") expect(t, fcache.Length() == 2, "The forum cache length should be 2")
_, err := c.Forums.Get(-1) _, err := c.Forums.Get(-1)
@ -601,7 +601,7 @@ func TestForumStore(t *testing.T) {
expect(t, fid == 3, "The first forum we create should have an ID of 3") expect(t, fid == 3, "The first forum we create should have an ID of 3")
expect(t, c.Forums.Exists(3), "FID #2 should exist") expect(t, c.Forums.Exists(3), "FID #2 should exist")
expect(t, c.Forums.GlobalCount() == 3, "The forumstore global count should be 3") expect(t, c.Forums.Count() == 3, "The forumstore global count should be 3")
expect(t, fcache.Length() == 3, "The forum cache length should be 3") expect(t, fcache.Length() == 3, "The forum cache length should be 3")
forum, err = c.Forums.Get(3) forum, err = c.Forums.Get(3)
@ -618,7 +618,7 @@ func TestForumStore(t *testing.T) {
expectNilErr(t, c.Forums.Delete(3)) expectNilErr(t, c.Forums.Delete(3))
expect(t, forum.ID == 3, fmt.Sprintf("forum pointer shenanigans")) expect(t, forum.ID == 3, fmt.Sprintf("forum pointer shenanigans"))
expect(t, c.Forums.GlobalCount() == 2, "The forumstore global count should be 2") expect(t, c.Forums.Count() == 2, "The forumstore global count should be 2")
expect(t, fcache.Length() == 2, "The forum cache length should be 2") expect(t, fcache.Length() == 2, "The forum cache length should be 2")
expect(t, !c.Forums.Exists(3), "FID #3 shouldn't exist after being deleted") expect(t, !c.Forums.Exists(3), "FID #3 shouldn't exist after being deleted")
_, err = c.Forums.Get(3) _, err = c.Forums.Get(3)
@ -938,10 +938,37 @@ func TestProfileReplyStore(t *testing.T) {
// TODO: Test profileReply.SetBody() and profileReply.Creator() // TODO: Test profileReply.SetBody() and profileReply.Creator()
} }
func TestActivityStream(t *testing.T) {
miscinit(t)
expect(t,c.Activity.Count()==0,"activity stream count should be 0")
_, err := c.Activity.Get(-1)
recordMustNotExist(t, err, "activity item -1 shouldn't exist")
_, err = c.Activity.Get(0)
recordMustNotExist(t, err, "activity item 0 shouldn't exist")
_, err = c.Activity.Get(1)
recordMustNotExist(t, err, "activity item 1 shouldn't exist")
a := c.Alert{ActorID: 1, TargetUserID: 1, Event: "like", ElementType: "topic", ElementID: 1}
id, err := c.Activity.Add(a)
expectNilErr(t,err)
expect(t,id==1,"new activity item id should be 1")
expect(t,c.Activity.Count()==1,"activity stream count should be 1")
alert, err := c.Activity.Get(1)
expectNilErr(t,err)
expect(t,alert.ActorID==1,"alert actorid should be 1")
expect(t,alert.TargetUserID==1,"alert targetuserid should be 1")
expect(t,alert.Event=="like","alert event type should be like")
expect(t,alert.ElementType=="topic","alert element type should be topic")
expect(t,alert.ElementID==1,"alert element id should be 1")
}
func TestLogs(t *testing.T) { func TestLogs(t *testing.T) {
miscinit(t) miscinit(t)
gTests := func(store c.LogStore, phrase string) { gTests := func(store c.LogStore, phrase string) {
expect(t, store.GlobalCount() == 0, "There shouldn't be any "+phrase) expect(t, store.Count() == 0, "There shouldn't be any "+phrase)
logs, err := store.GetOffset(0, 25) logs, err := store.GetOffset(0, 25)
expectNilErr(t, err) expectNilErr(t, err)
expect(t, len(logs) == 0, "The log slice should be empty") expect(t, len(logs) == 0, "The log slice should be empty")
@ -952,8 +979,8 @@ func TestLogs(t *testing.T) {
gTests2 := func(store c.LogStore, phrase string) { gTests2 := func(store c.LogStore, phrase string) {
err := store.Create("something", 0, "bumblefly", "::1", 1) err := store.Create("something", 0, "bumblefly", "::1", 1)
expectNilErr(t, err) expectNilErr(t, err)
count := store.GlobalCount() count := store.Count()
expect(t, count == 1, fmt.Sprintf("store.GlobalCount should return one, not %d", count)) expect(t, count == 1, fmt.Sprintf("store.Count should return one, not %d", count))
logs, err := store.GetOffset(0, 25) logs, err := store.GetOffset(0, 25)
recordMustExist(t, err, "We should have at-least one "+phrase) recordMustExist(t, err, "We should have at-least one "+phrase)
expect(t, len(logs) == 1, "The length of the log slice should be one") expect(t, len(logs) == 1, "The length of the log slice should be one")
@ -1159,7 +1186,7 @@ func TestWordFilters(t *testing.T) {
// TODO: Test the word filters and their store // TODO: Test the word filters and their store
expect(t, c.WordFilters.Length() == 0, "Word filter list should be empty") expect(t, c.WordFilters.Length() == 0, "Word filter list should be empty")
expect(t, c.WordFilters.EstCount() == 0, "Word filter list should be empty") expect(t, c.WordFilters.EstCount() == 0, "Word filter list should be empty")
expect(t, c.WordFilters.GlobalCount() == 0, "Word filter list should be empty") expect(t, c.WordFilters.Count() == 0, "Word filter list should be empty")
filters, err := c.WordFilters.GetAll() filters, err := c.WordFilters.GetAll()
expectNilErr(t, err) // TODO: Slightly confusing that we don't get ErrNoRow here expectNilErr(t, err) // TODO: Slightly confusing that we don't get ErrNoRow here
expect(t, len(filters) == 0, "Word filter map should be empty") expect(t, len(filters) == 0, "Word filter map should be empty")
@ -1169,7 +1196,7 @@ func TestWordFilters(t *testing.T) {
expectNilErr(t, err) expectNilErr(t, err)
expect(t, c.WordFilters.Length() == 1, "Word filter list should not be empty") expect(t, c.WordFilters.Length() == 1, "Word filter list should not be empty")
expect(t, c.WordFilters.EstCount() == 1, "Word filter list should not be empty") expect(t, c.WordFilters.EstCount() == 1, "Word filter list should not be empty")
expect(t, c.WordFilters.GlobalCount() == 1, "Word filter list should not be empty") expect(t, c.WordFilters.Count() == 1, "Word filter list should not be empty")
filters, err = c.WordFilters.GetAll() filters, err = c.WordFilters.GetAll()
expectNilErr(t, err) expectNilErr(t, err)
expect(t, len(filters) == 1, "Word filter map should not be empty") expect(t, len(filters) == 1, "Word filter map should not be empty")

View File

@ -6,7 +6,7 @@ func init() {
c.Plugins.Add(&c.Plugin{UName: "heythere", Name: "Hey There", Author: "Azareal", URL: "https://github.com/Azareal", Init: initHeythere, Deactivate: deactivateHeythere}) c.Plugins.Add(&c.Plugin{UName: "heythere", Name: "Hey There", Author: "Azareal", URL: "https://github.com/Azareal", Init: initHeythere, Deactivate: deactivateHeythere})
} }
// init_heythere is separate from init() as we don't want the plugin to run if the plugin is disabled // initHeythere is separate from init() as we don't want the plugin to run if the plugin is disabled
func initHeythere(plugin *c.Plugin) error { func initHeythere(plugin *c.Plugin) error {
plugin.AddHook("topic_reply_row_assign", heythereReply) plugin.AddHook("topic_reply_row_assign", heythereReply)
return nil return nil

View File

@ -714,7 +714,7 @@ func AccountEditEmailTokenSubmit(w http.ResponseWriter, r *http.Request, user c.
func AccountLogins(w http.ResponseWriter, r *http.Request, user c.User, header *c.Header) c.RouteError { func AccountLogins(w http.ResponseWriter, r *http.Request, user c.User, header *c.Header) c.RouteError {
accountEditHead("account_logins", w, r, &user, header) accountEditHead("account_logins", w, r, &user, header)
logCount := c.LoginLogs.Count(user.ID) logCount := c.LoginLogs.CountUser(user.ID)
page, _ := strconv.Atoi(r.FormValue("page")) page, _ := strconv.Atoi(r.FormValue("page"))
perPage := 12 perPage := 12
offset, page, lastPage := c.PageOffset(logCount, page, perPage) offset, page, lastPage := c.PageOffset(logCount, page, perPage)

View File

@ -111,27 +111,27 @@ func FootHeaders(w http.ResponseWriter, header *c.Header) {
} }
} }
func renderTemplate3(tmplName string, hookName string, w http.ResponseWriter, r *http.Request, header *c.Header, pi interface{}) error { func renderTemplate3(tmplName string, hookName string, w http.ResponseWriter, r *http.Request, h *c.Header, pi interface{}) error {
c.PrepResources(&header.CurrentUser, header, header.Theme) c.PrepResources(&h.CurrentUser, h, h.Theme)
if header.CurrentUser.Loggedin { if h.CurrentUser.Loggedin {
header.MetaDesc = "" h.MetaDesc = ""
header.OGDesc = "" h.OGDesc = ""
} else if header.MetaDesc != "" && header.OGDesc == "" { } else if h.MetaDesc != "" && h.OGDesc == "" {
header.OGDesc = header.MetaDesc h.OGDesc = h.MetaDesc
} }
header.AddScript("global.js") h.AddScript("global.js")
if header.CurrentUser.Loggedin { if h.CurrentUser.Loggedin {
header.AddScriptAsync("member.js") h.AddScriptAsync("member.js")
} }
FootHeaders(w, header) FootHeaders(w, h)
if header.CurrentUser.IsAdmin { if h.CurrentUser.IsAdmin {
header.Elapsed1 = time.Since(header.StartedAt).String() h.Elapsed1 = time.Since(h.StartedAt).String()
} }
if c.RunPreRenderHook("pre_render_"+hookName, w, r, &header.CurrentUser, pi) { if c.RunPreRenderHook("pre_render_"+hookName, w, r, &h.CurrentUser, pi) {
return nil return nil
} }
return header.Theme.RunTmpl(tmplName, pi, w) return h.Theme.RunTmpl(tmplName, pi, w)
} }
// TODO: Rename renderTemplate to RenderTemplate instead of using this hack to avoid breaking things // TODO: Rename renderTemplate to RenderTemplate instead of using this hack to avoid breaking things

View File

@ -124,11 +124,16 @@ func ViewForum(w http.ResponseWriter, r *http.Request, user c.User, header *c.He
pageList := c.Paginate(forum.TopicCount, c.Config.ItemsPerPage, 5) pageList := c.Paginate(forum.TopicCount, c.Config.ItemsPerPage, 5)
pi := c.ForumPage{header, topicList, forum, c.Paginator{pageList, page, lastPage}} pi := c.ForumPage{header, topicList, forum, c.Paginator{pageList, page, lastPage}}
var tmpl = forum.Tmpl tmpl := forum.Tmpl
if tmpl == "" { if tmpl == "" {
tmpl = "forum" ferr = renderTemplate("forum", w, r, header, pi)
} else {
tmpl = "forum_"+tmpl
err = renderTemplate3(tmpl, tmpl,w, r, header, pi)
if err != nil {
ferr = renderTemplate("forum", w, r, header, pi)
}
} }
ferr = renderTemplate(tmpl, w, r, header, pi)
counters.ForumViewCounter.Bump(forum.ID) counters.ForumViewCounter.Bump(forum.ID)
return ferr return ferr
} }

View File

@ -68,15 +68,16 @@ func CustomPage(w http.ResponseWriter, r *http.Request, user c.User, header *c.H
} else if err != sql.ErrNoRows { } else if err != sql.ErrNoRows {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
// ! Is this safe?
if c.DefaultTemplates.Lookup("page_"+name+".html") == nil {
return c.NotFound(w, r, header)
}
header.Title = phrases.GetTitlePhrase("page") header.Title = phrases.GetTitlePhrase("page")
// TODO: Pass the page name to the pre-render hook? // TODO: Pass the page name to the pre-render hook?
return renderTemplate2("page_"+name, "tmpl_page", w, r, header, c.Page{header, tList, nil}) err = renderTemplate3("page_"+name, "tmpl_page", w, r, header, c.Page{header, tList, nil})
if err == c.ErrBadDefaultTemplate {
return c.NotFound(w, r, header)
} else if err != nil {
return c.InternalError(err, w, r)
}
return nil
} }
// TODO: Set the cookie domain // TODO: Set the cookie domain

View File

@ -60,6 +60,31 @@ func Debug(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
} }
topicListThawed := c.TopicListThaw.Thawed() topicListThawed := c.TopicListThaw.Thawed()
pi := c.PanelDebugPage{basePage, goVersion, dbVersion, uptime, openConnCount, qgen.Builder.GetAdapter().GetName(), goroutines, cpus, memStats, tlen, ulen, rlen, tcap, ucap, rcap, topicListThawed} debugCache := c.DebugPageCache{tlen, ulen, rlen, tcap, ucap, rcap, topicListThawed}
debugDatabase := c.DebugPageDatabase{c.Topics.Count(),c.Users.Count(),c.Rstore.Count(),c.Prstore.Count(),c.Activity.Count()}
staticSize, err := c.DirSize("./public/")
if err != nil {
return c.InternalError(err,w,r)
}
attachSize, err := c.DirSize("./attachs/")
if err != nil {
return c.InternalError(err,w,r)
}
uploadsSize, err := c.DirSize("./uploads/")
if err != nil {
return c.InternalError(err,w,r)
}
logsSize, err := c.DirSize("./logs/")
if err != nil {
return c.InternalError(err,w,r)
}
backupsSize, err := c.DirSize("./backups/")
if err != nil {
return c.InternalError(err,w,r)
}
debugDisk := c.DebugPageDisk{staticSize,attachSize,uploadsSize,logsSize,backupsSize}
pi := c.PanelDebugPage{basePage, goVersion, dbVersion, uptime, openConnCount, qgen.Builder.GetAdapter().GetName(), goroutines, cpus, memStats, debugCache, debugDatabase, debugDisk}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_dashboard_right", "debug_page", "panel_debug", pi}) return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_dashboard_right", "debug_page", "panel_debug", pi})
} }

View File

@ -18,7 +18,7 @@ func LogsRegs(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError
return ferr return ferr
} }
logCount := c.RegLogs.GlobalCount() logCount := c.RegLogs.Count()
page, _ := strconv.Atoi(r.FormValue("page")) page, _ := strconv.Atoi(r.FormValue("page"))
perPage := 10 perPage := 10
offset, page, lastPage := c.PageOffset(logCount, page, perPage) offset, page, lastPage := c.PageOffset(logCount, page, perPage)
@ -107,7 +107,7 @@ func LogsMod(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
return ferr return ferr
} }
logCount := c.ModLogs.GlobalCount() logCount := c.ModLogs.Count()
page, _ := strconv.Atoi(r.FormValue("page")) page, _ := strconv.Atoi(r.FormValue("page"))
perPage := 10 perPage := 10
offset, page, lastPage := c.PageOffset(logCount, page, perPage) offset, page, lastPage := c.PageOffset(logCount, page, perPage)
@ -134,7 +134,7 @@ func LogsAdmin(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError
return ferr return ferr
} }
logCount := c.ModLogs.GlobalCount() logCount := c.ModLogs.Count()
page, _ := strconv.Atoi(r.FormValue("page")) page, _ := strconv.Atoi(r.FormValue("page"))
perPage := 10 perPage := 10
offset, page, lastPage := c.PageOffset(logCount, page, perPage) offset, page, lastPage := c.PageOffset(logCount, page, perPage)

View File

@ -21,7 +21,7 @@ func Pages(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
} }
// TODO: Test the pagination here // TODO: Test the pagination here
pageCount := c.Pages.GlobalCount() pageCount := c.Pages.Count()
page, _ := strconv.Atoi(r.FormValue("page")) page, _ := strconv.Atoi(r.FormValue("page"))
perPage := 15 perPage := 15
offset, page, lastPage := c.PageOffset(pageCount, page, perPage) offset, page, lastPage := c.PageOffset(pageCount, page, perPage)

View File

@ -7,6 +7,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"io" "io"
//"fmt"
"log" "log"
"net/http" "net/http"
"os" "os"
@ -148,7 +149,18 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user c.User, header *c.He
header.Zone = "view_topic" header.Zone = "view_topic"
header.ZoneID = topic.ID header.ZoneID = topic.ID
header.ZoneData = topic header.ZoneData = topic
rerr := renderTemplate("topic", w, r, header, tpage)
var rerr c.RouteError
tmpl := forum.Tmpl
if tmpl == "" {
rerr = renderTemplate("topic", w, r, header, tpage)
} else {
tmpl = "topic_"+tmpl
err = renderTemplate3(tmpl,tmpl, w, r, header, tpage)
if err != nil {
rerr = renderTemplate("topic", w, r, header, tpage)
}
}
counters.TopicViewCounter.Bump(topic.ID) // TODO: Move this into the router? counters.TopicViewCounter.Bump(topic.ID) // TODO: Move this into the router?
counters.ForumViewCounter.Bump(topic.ParentID) counters.ForumViewCounter.Bump(topic.ParentID)
return rerr return rerr
@ -848,11 +860,13 @@ func addTopicAction(action string, topic *c.Topic, user c.User) error {
// TODO: Refactor this // TODO: Refactor this
func LikeTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid string) c.RouteError { func LikeTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid string) c.RouteError {
//fmt.Println("i1")
isJs := (r.PostFormValue("isJs") == "1") isJs := (r.PostFormValue("isJs") == "1")
tid, err := strconv.Atoi(stid) tid, err := strconv.Atoi(stid)
if err != nil { if err != nil {
return c.PreErrorJSQ(phrases.GetErrorPhrase("id_must_be_integer"), w, r, isJs) return c.PreErrorJSQ(phrases.GetErrorPhrase("id_must_be_integer"), w, r, isJs)
} }
//fmt.Println("i2")
topic, err := c.Topics.Get(tid) topic, err := c.Topics.Get(tid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
@ -860,6 +874,7 @@ func LikeTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid s
} else if err != nil { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, isJs)
} }
//fmt.Println("i3")
// TODO: Add hooks to make use of headerLite // TODO: Add hooks to make use of headerLite
lite, ferr := c.SimpleForumUserCheck(w, r, &user, topic.ParentID) lite, ferr := c.SimpleForumUserCheck(w, r, &user, topic.ParentID)
@ -872,6 +887,7 @@ func LikeTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid s
if topic.CreatedBy == user.ID { if topic.CreatedBy == user.ID {
return c.LocalErrorJSQ("You can't like your own topics", w, r, user, isJs) return c.LocalErrorJSQ("You can't like your own topics", w, r, user, isJs)
} }
//fmt.Println("i4")
_, err = c.Users.Get(topic.CreatedBy) _, err = c.Users.Get(topic.CreatedBy)
if err != nil && err == sql.ErrNoRows { if err != nil && err == sql.ErrNoRows {
@ -879,6 +895,7 @@ func LikeTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid s
} else if err != nil { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, isJs)
} }
//fmt.Println("i5")
score := 1 score := 1
err = topic.Like(score, user.ID) err = topic.Like(score, user.ID)
@ -887,6 +904,7 @@ func LikeTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid s
} else if err != nil { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, isJs)
} }
//fmt.Println("i6")
// ! Be careful about leaking per-route permission state with &user // ! Be careful about leaking per-route permission state with &user
alert := c.Alert{ActorID: user.ID, TargetUserID: topic.CreatedBy, Event: "like", ElementType: "topic", ElementID: tid, Actor: &user} alert := c.Alert{ActorID: user.ID, TargetUserID: topic.CreatedBy, Event: "like", ElementType: "topic", ElementID: tid, Actor: &user}
@ -894,11 +912,13 @@ func LikeTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid s
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, isJs)
} }
//fmt.Println("i7")
skip, rerr := lite.Hooks.VhookSkippable("action_end_like_topic", topic.ID, &user) skip, rerr := lite.Hooks.VhookSkippable("action_end_like_topic", topic.ID, &user)
if skip || rerr != nil { if skip || rerr != nil {
return rerr return rerr
} }
//fmt.Println("i8")
if !isJs { if !isJs {
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther) http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)

View File

@ -43,8 +43,8 @@
</div> </div>
{{end}} {{end}}
{{end}} {{end}}
<div id="forum_topic_list" class="rowblock micro_grid" aria-label="{{lang "forum_list_aria"}}"> <div id="forum_topic_list" class="rowblock micro_grid" aria-label="{{lang "forum_list_aria"}}" style="grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));">
{{range .ItemList}}<div class="rowitem" data-tid="{{.ID}}" style="grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));"> {{range .ItemList}}<div class="rowitem" data-tid="{{.ID}}">
<div> <div>
<a class="rowtopic" href="{{.Link}}" itemprop="itemListElement"><img src="{{.Content}}" style="width:100%;height:160px;" /></a> <a class="rowtopic" href="{{.Link}}" itemprop="itemListElement"><img src="{{.Content}}" style="width:100%;height:160px;" /></a>
<br /><a class="rowsmall starter" href="{{.Link}}">{{.Title}}</a> <br /><a class="rowsmall starter" href="{{.Link}}">{{.Title}}</a>

View File

@ -77,16 +77,58 @@
<div class="grid_item grid_stat grid_stat_head"><span>User Cache</span></div> <div class="grid_item grid_stat grid_stat_head"><span>User Cache</span></div>
<div class="grid_item grid_stat grid_stat_head"><span>Reply Cache</span></div> <div class="grid_item grid_stat grid_stat_head"><span>Reply Cache</span></div>
<div class="grid_item grid_stat"><span>{{.TCache}} / {{.TCap}}</span></div> <div class="grid_item grid_stat"><span>{{.Cache.Topics}} / {{.Cache.TCap}}</span></div>
<div class="grid_item grid_stat"><span>{{.UCache}} / {{.UCap}}</span></div> <div class="grid_item grid_stat"><span>{{.Cache.Users}} / {{.Cache.UCap}}</span></div>
<div class="grid_item grid_stat"><span>{{.RCache}} / {{.RCap}}</span></div> <div class="grid_item grid_stat"><span>{{.Cache.Replies}} / {{.Cache.RCap}}</span></div>
<div class="grid_item grid_stat grid_stat_head"><span>Topic List</span></div> <div class="grid_item grid_stat grid_stat_head"><span>Topic List</span></div>
<div class="grid_item grid_stat grid_stat_head"><span>???</span></div> <div class="grid_item grid_stat grid_stat_head"><span>???</span></div>
<div class="grid_item grid_stat grid_stat_head"><span>???</span></div> <div class="grid_item grid_stat grid_stat_head"><span>???</span></div>
<div class="grid_item grid_stat"><span>{{if .TopicListThaw}}Thawed{{else}}Sleeping{{end}}</span></div> <div class="grid_item grid_stat"><span>{{if .Cache.TopicListThaw}}Thawed{{else}}Sleeping{{end}}</span></div>
<div class="grid_item grid_stat"><span>?</span></div> <div class="grid_item grid_stat"><span>?</span></div>
<div class="grid_item grid_stat"><span>?</span></div> <div class="grid_item grid_stat"><span>?</span></div>
</div> </div>
<div class="colstack_item colstack_head colstack_sub_head">
<div class="rowitem"><h2>Database</h2></div>
</div>
<div id="panel_debug" class="colstack_grid">
<div class="grid_item grid_stat grid_stat_head"><span>Topics</span></div>
<div class="grid_item grid_stat grid_stat_head"><span>Users</span></div>
<div class="grid_item grid_stat grid_stat_head"><span>Replies</span></div>
<div class="grid_item grid_stat"><span>{{.Database.Topics}}</span></div>
<div class="grid_item grid_stat"><span>{{.Database.Users}}</span></div>
<div class="grid_item grid_stat"><span>{{.Database.Replies}}</span></div>
<div class="grid_item grid_stat grid_stat_head"><span>Profile Replies</span></div>
<div class="grid_item grid_stat grid_stat_head"><span>Activity Stream</span></div>
<div class="grid_item grid_stat grid_stat_head"><span>???</span></div>
<div class="grid_item grid_stat"><span>{{.Database.ProfileReplies}}</span></div>
<div class="grid_item grid_stat"><span>{{.Database.ActivityStream}}</span></div>
<div class="grid_item grid_stat"><span>?</span></div>
</div>
<div class="colstack_item colstack_head colstack_sub_head">
<div class="rowitem"><h2>Disk</h2></div>
</div>
<div id="panel_debug" class="colstack_grid">
<div class="grid_item grid_stat grid_stat_head"><span>Static Files</span></div>
<div class="grid_item grid_stat grid_stat_head"><span>Attachments</span></div>
<div class="grid_item grid_stat grid_stat_head"><span>Avatars</span></div>
<div class="grid_item grid_stat"><span>{{bunit .Disk.Static}}</span></div>
<div class="grid_item grid_stat"><span>{{bunit .Disk.Attachments}}</span></div>
<div class="grid_item grid_stat"><span>{{bunit .Disk.Avatars}}</span></div>
<div class="grid_item grid_stat grid_stat_head"><span>Log Files</span></div>
<div class="grid_item grid_stat grid_stat_head"><span>Backups</span></div>
<div class="grid_item grid_stat grid_stat_head"><span>???</span></div>
<div class="grid_item grid_stat"><span>{{bunit .Disk.Logs}}</span></div>
<div class="grid_item grid_stat"><span>{{bunit .Disk.Backups}}</span></div>
<div class="grid_item grid_stat"><span>?</span></div>
</div>