From 05c2ac3ce4bbade284661cb0fddc9e75392fbc26 Mon Sep 17 00:00:00 2001 From: Azareal Date: Sat, 1 Jun 2019 22:31:48 +1000 Subject: [PATCH] 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. --- common/activity_stream.go | 52 +++++++++++++++++++++++++++++++++++ common/alerts.go | 42 +++++++++++----------------- common/attachments.go | 14 +++++----- common/audit_logs.go | 26 +++++++++--------- common/cache.go | 2 +- common/forum_store.go | 10 +++---- common/group_store.go | 14 +++++----- common/misc_logs.go | 24 ++++++++-------- common/page_store.go | 10 +++---- common/pages.go | 40 +++++++++++++++++++++------ common/poll_store.go | 6 ++-- common/profile_reply_store.go | 28 +++++++++++++------ common/reply_store.go | 26 +++++++++++++----- common/routes_common.go | 8 +++--- common/template_init.go | 2 ++ common/theme.go | 4 +++ common/topic_store.go | 14 +++++----- common/user_store.go | 24 ++++++++-------- common/utils.go | 15 ++++++++++ common/widget.go | 16 +++++++++-- common/widgets.go | 4 +-- common/word_filters.go | 48 ++++++++++++++++---------------- main.go | 6 +++- misc_test.go | 49 +++++++++++++++++++++++++-------- plugin_heythere.go | 2 +- routes/account.go | 2 +- routes/common.go | 30 ++++++++++---------- routes/forum.go | 11 ++++++-- routes/misc.go | 17 ++++++------ routes/panel/debug.go | 27 +++++++++++++++++- routes/panel/logs.go | 6 ++-- routes/panel/pages.go | 2 +- routes/topic.go | 22 ++++++++++++++- templates/forum_gallery.html | 4 +-- templates/panel_debug.html | 50 ++++++++++++++++++++++++++++++--- 35 files changed, 451 insertions(+), 206 deletions(-) create mode 100644 common/activity_stream.go diff --git a/common/activity_stream.go b/common/activity_stream.go new file mode 100644 index 00000000..dcf30f2f --- /dev/null +++ b/common/activity_stream.go @@ -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 +} \ No newline at end of file diff --git a/common/alerts.go b/common/alerts.go index 11e9cdaf..ab40b82e 100644 --- a/common/alerts.go +++ b/common/alerts.go @@ -12,6 +12,7 @@ import ( "strconv" "strings" "time" + //"fmt" "github.com/Azareal/Gosora/common/phrases" "github.com/Azareal/Gosora/query_gen" @@ -30,11 +31,9 @@ type Alert struct { } type AlertStmts struct { - addActivity *sql.Stmt notifyWatchers *sql.Stmt notifyOne *sql.Stmt getWatchers *sql.Stmt - getActivityEntry *sql.Stmt } var alertStmts AlertStmts @@ -44,14 +43,12 @@ var alertStmts AlertStmts func init() { DbInits.Add(func(acc *qgen.Accumulator) error { alertStmts = AlertStmts{ - addActivity: acc.Insert("activity_stream").Columns("actor, targetUser, event, elementType, elementID, createdAt").Fields("?,?,?,?,?,UTC_TIMESTAMP()").Prepare(), notifyWatchers: acc.SimpleInsertInnerJoin( 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 = ?", "", ""}, ), 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 = ?", "", ""), - getActivityEntry: acc.Select("activity_stream").Columns("actor, targetUser, event, elementType, elementID, createdAt").Where("asid = ?").Prepare(), } return acc.FirstError() }) @@ -79,7 +76,6 @@ func BuildAlert(alert Alert, user User /* The current user */) (out string, err return } }*/ - if alert.Event == "friend_invite" { 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 { - 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 { return err } - lastID, err := res.LastInsertId() - if err != nil { - return err - } - return NotifyWatchers(lastID) + return NotifyWatchers(id) } 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 { return err } - lastID, err := res.LastInsertId() + err = NotifyOne(alert.TargetUserID, id) if err != nil { return err } - - err = NotifyOne(alert.TargetUserID, lastID) - if err != nil { - return err - } - alert.ASID = int(lastID) + alert.ASID = id // Live alerts, if the target is online and WebSockets is enabled - _ = WsHub.pushAlert(alert.TargetUserID, alert) + if EnableWebsockets { + go func() { + _ = WsHub.pushAlert(alert.TargetUserID, alert) + //fmt.Println("err:",err) + }() + } return nil } -func NotifyOne(watcher int, asid int64) error { +func NotifyOne(watcher int, asid int) error { _, err := alertStmts.notifyOne.Exec(watcher, asid) return err } -func NotifyWatchers(asid int64) error { +func NotifyWatchers(asid int) error { _, err := alertStmts.notifyWatchers.Exec(asid) if err != nil { return err @@ -214,7 +206,7 @@ func NotifyWatchers(asid int64) error { return nil } -func notifyWatchers(asid int64) { +func notifyWatchers(asid int) { rows, err := alertStmts.getWatchers.Query(asid) if err != nil && err != ErrNoRows { LogError(err) @@ -238,12 +230,10 @@ func notifyWatchers(asid int64) { return } - var alert = Alert{ASID: int(asid)} - err = alertStmts.getActivityEntry.QueryRow(asid).Scan(&alert.ActorID, &alert.TargetUserID, &alert.Event, &alert.ElementType, &alert.ElementID, &alert.CreatedAt) + alert, err := Activity.Get(asid) if err != nil && err != ErrNoRows { LogError(err) return } - _ = WsHub.pushAlerts(uids, alert) } diff --git a/common/attachments.go b/common/attachments.go index f3ee2afb..49b93322 100644 --- a/common/attachments.go +++ b/common/attachments.go @@ -29,7 +29,7 @@ type AttachmentStore interface { 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 MoveToByExtra(sectionID int, originTable string, extra string) error - GlobalCount() int + Count() int CountIn(originTable string, oid int) int CountInPath(path string) int Delete(aid int) error @@ -149,18 +149,18 @@ func (store *DefaultAttachmentStore) Add(sectionID int, sectionTable string, ori return int(lid), err } -func (store *DefaultAttachmentStore) MoveTo(sectionID int, originID int, originTable string) error { - _, err := store.move.Exec(sectionID, originID, originTable) +func (s *DefaultAttachmentStore) MoveTo(sectionID int, originID int, originTable string) error { + _, err := s.move.Exec(sectionID, originID, originTable) return err } -func (store *DefaultAttachmentStore) MoveToByExtra(sectionID int, originTable string, extra string) error { - _, err := store.moveByExtra.Exec(sectionID, originTable, extra) +func (s *DefaultAttachmentStore) MoveToByExtra(sectionID int, originTable string, extra string) error { + _, err := s.moveByExtra.Exec(sectionID, originTable, extra) return err } -func (store *DefaultAttachmentStore) GlobalCount() (count int) { - err := store.count.QueryRow().Scan(&count) +func (s *DefaultAttachmentStore) Count() (count int) { + err := s.count.QueryRow().Scan(&count) if err != nil { LogError(err) } diff --git a/common/audit_logs.go b/common/audit_logs.go index dda02669..125f4785 100644 --- a/common/audit_logs.go +++ b/common/audit_logs.go @@ -21,7 +21,7 @@ type LogItem struct { type LogStore interface { 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) } @@ -40,17 +40,17 @@ func NewModLogStore(acc *qgen.Accumulator) (*SQLModLogStore, error) { } // TODO: Make a store for this? -func (store *SQLModLogStore) Create(action string, elementID int, elementType string, ipaddress string, actorID int) (err error) { - _, err = store.create.Exec(action, elementID, elementType, ipaddress, actorID) +func (s *SQLModLogStore) Create(action string, elementID int, elementType string, ipaddress string, actorID int) (err error) { + _, err = s.create.Exec(action, elementID, elementType, ipaddress, actorID) return err } -func (store *SQLModLogStore) GlobalCount() (logCount int) { - err := store.count.QueryRow().Scan(&logCount) +func (s *SQLModLogStore) Count() (count int) { + err := s.count.QueryRow().Scan(&count) if err != nil { LogError(err) } - return logCount + return count } 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? -func (store *SQLAdminLogStore) Create(action string, elementID int, elementType string, ipaddress string, actorID int) (err error) { - _, err = store.create.Exec(action, elementID, elementType, ipaddress, actorID) +func (s *SQLAdminLogStore) Create(action string, elementID int, elementType string, ipaddress string, actorID int) (err error) { + _, err = s.create.Exec(action, elementID, elementType, ipaddress, actorID) return err } -func (store *SQLAdminLogStore) GlobalCount() (logCount int) { - err := store.count.QueryRow().Scan(&logCount) +func (s *SQLAdminLogStore) Count() (count int) { + err := s.count.QueryRow().Scan(&count) if err != nil { LogError(err) } - return logCount + return count } -func (store *SQLAdminLogStore) GetOffset(offset int, perPage int) (logs []LogItem, err error) { - rows, err := store.getOffset.Query(offset, perPage) +func (s *SQLAdminLogStore) GetOffset(offset int, perPage int) (logs []LogItem, err error) { + rows, err := s.getOffset.Query(offset, perPage) if err != nil { return logs, err } diff --git a/common/cache.go b/common/cache.go index ea048c78..0fc6dbdd 100644 --- a/common/cache.go +++ b/common/cache.go @@ -14,7 +14,7 @@ type DataStore interface { DirtyGet(id int) interface{} Get(id int) (interface{}, error) BypassGet(id int) (interface{}, error) - //GlobalCount() + //Count() int } // nolint diff --git a/common/forum_store.go b/common/forum_store.go index 9fe0663f..49cb52e1 100644 --- a/common/forum_store.go +++ b/common/forum_store.go @@ -47,7 +47,7 @@ type ForumStore interface { Create(forumName string, forumDesc string, active bool, preset string) (int, error) UpdateOrder(updateMap map[int]int) error - GlobalCount() int + Count() int } 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? -// GlobalCount returns the total number of forums -func (s *MemoryForumStore) GlobalCount() (fcount int) { - err := s.count.QueryRow().Scan(&fcount) +// Count returns the total number of forums +func (s *MemoryForumStore) Count() (count int) { + err := s.count.QueryRow().Scan(&count) if err != nil { LogError(err) } - return fcount + return count } // TODO: Work on SqlForumStore diff --git a/common/group_store.go b/common/group_store.go index edcadff0..c95f65e8 100644 --- a/common/group_store.go +++ b/common/group_store.go @@ -25,7 +25,7 @@ type GroupStore interface { GetAll() ([]*Group, error) GetRange(lower int, higher int) ([]*Group, error) Reload(id int) error // ? - Should we move this to GroupCache? It might require us to do some unnecessary casting though - GlobalCount() int + Count() int } type GroupCache interface { @@ -336,14 +336,14 @@ func (mgs *MemoryGroupStore) GetRange(lower int, higher int) (groups []*Group, e return groups, nil } -func (mgs *MemoryGroupStore) Length() int { - mgs.RLock() - defer mgs.RUnlock() - return mgs.groupCount +func (s *MemoryGroupStore) Length() int { + s.RLock() + defer s.RUnlock() + return s.groupCount } -func (mgs *MemoryGroupStore) GlobalCount() (count int) { - err := mgs.count.QueryRow().Scan(&count) +func (s *MemoryGroupStore) Count() (count int) { + err := s.count.QueryRow().Scan(&count) if err != nil { LogError(err) } diff --git a/common/misc_logs.go b/common/misc_logs.go index ea67ceb9..6230caae 100644 --- a/common/misc_logs.go +++ b/common/misc_logs.go @@ -55,7 +55,7 @@ func (log *RegLogItem) Create() (id int, err error) { } type RegLogStore interface { - GlobalCount() (logCount int) + Count() (count int) GetOffset(offset int, perPage int) (logs []RegLogItem, err error) } @@ -71,12 +71,12 @@ func NewRegLogStore(acc *qgen.Accumulator) (*SQLRegLogStore, error) { }, acc.FirstError() } -func (store *SQLRegLogStore) GlobalCount() (logCount int) { - err := store.count.QueryRow().Scan(&logCount) +func (s *SQLRegLogStore) Count() (count int) { + err := s.count.QueryRow().Scan(&count) if err != nil { LogError(err) } - return logCount + return count } 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 { - GlobalCount() (logCount int) - Count(uid int) (logCount int) + Count() (count int) + CountUser(uid int) (count int) GetOffset(uid int, offset int, perPage int) (logs []LoginLogItem, err error) } @@ -161,20 +161,20 @@ func NewLoginLogStore(acc *qgen.Accumulator) (*SQLLoginLogStore, error) { }, acc.FirstError() } -func (store *SQLLoginLogStore) GlobalCount() (logCount int) { - err := store.count.QueryRow().Scan(&logCount) +func (s *SQLLoginLogStore) Count() (count int) { + err := s.count.QueryRow().Scan(&count) if err != nil { LogError(err) } - return logCount + return count } -func (store *SQLLoginLogStore) Count(uid int) (logCount int) { - err := store.countForUser.QueryRow(uid).Scan(&logCount) +func (s *SQLLoginLogStore) CountUser(uid int) (count int) { + err := s.countForUser.QueryRow(uid).Scan(&count) if err != nil { LogError(err) } - return logCount + return count } func (store *SQLLoginLogStore) GetOffset(uid int, offset int, perPage int) (logs []LoginLogItem, err error) { diff --git a/common/page_store.go b/common/page_store.go index 5965b3a4..d7a8db5e 100644 --- a/common/page_store.go +++ b/common/page_store.go @@ -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 type PageStore interface { - GlobalCount() (pageCount int) + Count() (count int) Get(id int) (*CustomPage, error) GetByName(name string) (*CustomPage, error) GetOffset(offset int, perPage int) (pages []*CustomPage, err error) @@ -99,15 +99,15 @@ func NewDefaultPageStore(acc *qgen.Accumulator) (*DefaultPageStore, error) { }, acc.FirstError() } -func (store *DefaultPageStore) GlobalCount() (pageCount int) { - err := store.count.QueryRow().Scan(&pageCount) +func (s *DefaultPageStore) Count() (count int) { + err := s.count.QueryRow().Scan(&count) if err != nil { 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 == "" { return nil } diff --git a/common/pages.go b/common/pages.go index 37c1a7ce..94261c59 100644 --- a/common/pages.go +++ b/common/pages.go @@ -594,6 +594,34 @@ type PanelRegLogsPage struct { 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 { *BasePanelPage GoVersion string @@ -607,15 +635,9 @@ type PanelDebugPage struct { CPUs int MemStats runtime.MemStats - TCache int - UCache int - RCache int - - TCap int - UCap int - RCap int - - TopicListThaw bool + Cache DebugPageCache + Database DebugPageDatabase + Disk DebugPageDisk } type PageSimple struct { diff --git a/common/poll_store.go b/common/poll_store.go index 146a7ecc..cc23b69f 100644 --- a/common/poll_store.go +++ b/common/poll_store.go @@ -51,7 +51,7 @@ type PollStore interface { Create(parent Pollable, pollType int, pollOptions map[int]string) (int, error) CastVote(optionIndex int, pollID int, uid int, ipaddress string) error Reload(id int) error - //GlobalCount() int + //Count() int SetCache(cache PollCache) GetCache() PollCache @@ -68,7 +68,7 @@ type DefaultPollStore struct { incrementVoteCount *sql.Stmt incrementVoteCountForOption *sql.Stmt delete *sql.Stmt - //pollCount *sql.Stmt + //count *sql.Stmt } 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(), 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(), - //pollCount: acc.SimpleCount("polls", "", ""), + //count: acc.SimpleCount("polls", "", ""), }, acc.FirstError() } diff --git a/common/profile_reply_store.go b/common/profile_reply_store.go index 9b2c99f5..3483a262 100644 --- a/common/profile_reply_store.go +++ b/common/profile_reply_store.go @@ -3,7 +3,7 @@ package common import ( "database/sql" - "github.com/Azareal/Gosora/query_gen" + qgen "github.com/Azareal/Gosora/query_gen" ) var Prstore ProfileReplyStore @@ -11,6 +11,7 @@ var Prstore ProfileReplyStore type ProfileReplyStore interface { Get(id int) (*ProfileReply, 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 @@ -18,23 +19,25 @@ type ProfileReplyStore interface { type SQLProfileReplyStore struct { get *sql.Stmt create *sql.Stmt + count *sql.Stmt } func NewSQLProfileReplyStore(acc *qgen.Accumulator) (*SQLProfileReplyStore, error) { return &SQLProfileReplyStore{ 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(), + count: acc.Count("users_replies").Prepare(), }, acc.FirstError() } -func (store *SQLProfileReplyStore) Get(id int) (*ProfileReply, error) { - reply := ProfileReply{ID: id} - err := store.get.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IPAddress) - return &reply, err +func (s *SQLProfileReplyStore) Get(id int) (*ProfileReply, error) { + r := ProfileReply{ID: id} + err := s.get.QueryRow(id).Scan(&r.ParentID, &r.Content, &r.CreatedBy, &r.CreatedAt, &r.LastEdit, &r.LastEditBy, &r.IPAddress) + return &r, err } -func (store *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) +func (s *SQLProfileReplyStore) Create(profileID int, content string, createdBy int, ipaddress string) (id int, err error) { + res, err := s.create.Exec(profileID, content, ParseMessage(content, 0, ""), createdBy, ipaddress) if err != nil { return 0, err } @@ -42,7 +45,16 @@ func (store *SQLProfileReplyStore) Create(profileID int, content string, created if err != nil { return 0, err } - // Should we reload the user? 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 +} diff --git a/common/reply_store.go b/common/reply_store.go index e0c08af4..d73a7b67 100644 --- a/common/reply_store.go +++ b/common/reply_store.go @@ -9,6 +9,7 @@ var Rstore ReplyStore type ReplyStore interface { Get(id int) (*Reply, error) Create(topic *Topic, content string, ipaddress string, uid int) (id int, err error) + Count() (count int) SetCache(cache ReplyCache) GetCache() ReplyCache @@ -19,6 +20,7 @@ type SQLReplyStore struct { get *sql.Stmt create *sql.Stmt + count *sql.Stmt } func NewSQLReplyStore(acc *qgen.Accumulator, cache ReplyCache) (*SQLReplyStore, error) { @@ -29,22 +31,22 @@ func NewSQLReplyStore(acc *qgen.Accumulator, cache ReplyCache) (*SQLReplyStore, cache: cache, 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(), + count: acc.Count("replies").Prepare(), }, acc.FirstError() } func (s *SQLReplyStore) Get(id int) (*Reply, error) { - //log.Print("SQLReplyStore.Get") - reply, err := s.cache.Get(id) + r, err := s.cache.Get(id) if err == nil { - return reply, nil + return r, nil } - reply = &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) + 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) if err == nil { - _ = s.cache.Set(reply) + _ = s.cache.Set(r) } - return reply, err + return r, err } // 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) } +// 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) { s.cache = cache } diff --git a/common/routes_common.go b/common/routes_common.go index 522105e0..a74e234f 100644 --- a/common/routes_common.go +++ b/common/routes_common.go @@ -138,10 +138,10 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header //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 - stats.Users = Users.GlobalCount() - stats.Groups = Groups.GlobalCount() - stats.Forums = Forums.GlobalCount() - stats.Pages = Pages.GlobalCount() + stats.Users = Users.Count() + stats.Groups = Groups.Count() + stats.Forums = Forums.Count() + stats.Pages = Pages.Count() stats.Settings = len(header.Settings) stats.WordFilters = WordFilters.EstCount() stats.Themes = len(Themes) diff --git a/common/template_init.go b/common/template_init.go index f4108a40..fe37aa39 100644 --- a/common/template_init.go +++ b/common/template_init.go @@ -745,6 +745,8 @@ func initDefaultTmplFuncMap() { byteFloat, unit = ConvertByteUnit(float64(bytes)) case uint64: byteFloat, unit = ConvertByteUnit(float64(bytes)) + case float64: + byteFloat, unit = ConvertByteUnit(bytes) default: panic("bytes is not an int, int64 or uint64") } diff --git a/common/theme.go b/common/theme.go index 4e72b436..21efe8a5 100644 --- a/common/theme.go +++ b/common/theme.go @@ -23,6 +23,7 @@ import ( ) 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 { 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 { mapping = template } + if theme.IntTmplHandle.Lookup(mapping+".html") == nil { + return ErrBadDefaultTemplate + } return theme.IntTmplHandle.ExecuteTemplate(w, mapping+".html", pi) default: log.Print("theme ", theme) diff --git a/common/topic_store.go b/common/topic_store.go index 1ee350f1..41824fda 100644 --- a/common/topic_store.go +++ b/common/topic_store.go @@ -36,7 +36,7 @@ type TopicStore interface { // TODO: Implement these two methods //Replies(tid int) ([]*Reply, error) //RepliesRange(tid int, lower int, higher int) ([]*Reply, error) - GlobalCount() int + Count() int SetCache(cache TopicCache) GetCache() TopicCache @@ -47,7 +47,7 @@ type DefaultTopicStore struct { get *sql.Stmt exists *sql.Stmt - topicCount *sql.Stmt + count *sql.Stmt create *sql.Stmt } @@ -61,7 +61,7 @@ func NewDefaultTopicStore(cache TopicCache) (*DefaultTopicStore, error) { 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(), 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(), }, acc.FirstError() } @@ -239,13 +239,13 @@ func (mts *DefaultTopicStore) AddLastTopic(item *Topic, fid int) error { return nil } -// GlobalCount returns the total number of topics on these forums -func (mts *DefaultTopicStore) GlobalCount() (tcount int) { - err := mts.topicCount.QueryRow().Scan(&tcount) +// Count returns the total number of topics on these forums +func (s *DefaultTopicStore) Count() (count int) { + err := s.count.QueryRow().Scan(&count) if err != nil { LogError(err) } - return tcount + return count } func (mts *DefaultTopicStore) SetCache(cache TopicCache) { diff --git a/common/user_store.go b/common/user_store.go index 58e53b26..7a15620e 100644 --- a/common/user_store.go +++ b/common/user_store.go @@ -26,7 +26,7 @@ type UserStore interface { BypassGet(id int) (*User, error) Create(username string, password string, email string, group int, active bool) (int, error) Reload(id int) error - GlobalCount() int + Count() int SetCache(cache UserCache) GetCache() UserCache @@ -41,7 +41,7 @@ type DefaultUserStore struct { exists *sql.Stmt register *sql.Stmt usernameExists *sql.Stmt - userCount *sql.Stmt + count *sql.Stmt } // NewDefaultUserStore gives you a new instance of DefaultUserStore @@ -59,7 +59,7 @@ func NewDefaultUserStore(cache UserCache) (*DefaultUserStore, error) { 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 usernameExists: acc.SimpleSelect("users", "name", "name = ?", "", ""), - userCount: acc.Count("users").Prepare(), + count: acc.Count("users").Prepare(), }, acc.FirstError() } @@ -282,24 +282,24 @@ func (mus *DefaultUserStore) Create(username string, password string, email stri return int(lastID), err } -// GlobalCount returns the total number of users registered on the forums -func (mus *DefaultUserStore) GlobalCount() (ucount int) { - err := mus.userCount.QueryRow().Scan(&ucount) +// Count returns the total number of users registered on the forums +func (s *DefaultUserStore) Count() (count int) { + err := s.count.QueryRow().Scan(&count) if err != nil { LogError(err) } - return ucount + return count } -func (mus *DefaultUserStore) SetCache(cache UserCache) { - mus.cache = cache +func (s *DefaultUserStore) SetCache(cache UserCache) { + s.cache = cache } // TODO: We're temporarily doing this so that you can do ucache != nil in getTopicUser. Refactor it. -func (mus *DefaultUserStore) GetCache() UserCache { - _, ok := mus.cache.(*NullUserCache) +func (s *DefaultUserStore) GetCache() UserCache { + _, ok := s.cache.(*NullUserCache) if ok { return nil } - return mus.cache + return s.cache } diff --git a/common/utils.go b/common/utils.go index 397fc532..b24bfa4b 100644 --- a/common/utils.go +++ b/common/utils.go @@ -10,6 +10,7 @@ import ( "crypto/rand" "encoding/base32" "encoding/base64" + "path/filepath" "errors" "fmt" "html" @@ -509,3 +510,17 @@ func BuildSlug(slug string, id int) string { } 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 +} \ No newline at end of file diff --git a/common/widget.go b/common/widget.go index 6c332df5..e721b66a 100644 --- a/common/widget.go +++ b/common/widget.go @@ -4,6 +4,7 @@ import ( "database/sql" "encoding/json" "strings" + "strconv" "sync/atomic" "github.com/Azareal/Gosora/query_gen" @@ -76,11 +77,22 @@ func (widget *Widget) Copy() (owidget *Widget) { // 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 -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, "|") { + 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 { return true - } else if len(loc) > 0 && loc[0] == '!' { + } else if loc[0] == '!' { loc = loc[1:] if loc != "global" && loc != zone { return true diff --git a/common/widgets.go b/common/widgets.go index fa9314bc..f8378ba0 100644 --- a/common/widgets.go +++ b/common/widgets.go @@ -166,7 +166,7 @@ func HasWidgets(dock string, header *Header) bool { if !widget.Enabled { continue } - if widget.Allowed(header.Zone) { + if widget.Allowed(header.Zone,header.ZoneID) { wcount++ } } @@ -210,7 +210,7 @@ func BuildWidget(dock string, header *Header) (sbody string) { if !widget.Enabled { continue } - if widget.Allowed(header.Zone) { + if widget.Allowed(header.Zone,header.ZoneID) { item, err := widget.Build(header) if err != nil { LogError(err) diff --git a/common/word_filters.go b/common/word_filters.go index e22d7ec9..cb68ee7d 100644 --- a/common/word_filters.go +++ b/common/word_filters.go @@ -24,7 +24,7 @@ type WordFilterStore interface { Update(id int, find string, replacement string) error Length() int EstCount() int - GlobalCount() (count int) + Count() (count int) } 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 -func (store *DefaultWordFilterStore) ReloadAll() error { +func (s *DefaultWordFilterStore) ReloadAll() error { var wordFilters = make(map[int]*WordFilter) - filters, err := store.bypassGetAll() + filters, err := s.bypassGetAll() if err != nil { return err } @@ -64,13 +64,13 @@ func (store *DefaultWordFilterStore) ReloadAll() error { wordFilters[filter.ID] = filter } - store.box.Store(wordFilters) + s.box.Store(wordFilters) return nil } // ? - Return pointers to word filters intead to save memory? -- A map is a pointer. -func (store *DefaultWordFilterStore) bypassGetAll() (filters []*WordFilter, err error) { - rows, err := store.getAll.Query() +func (s *DefaultWordFilterStore) bypassGetAll() (filters []*WordFilter, err error) { + rows, err := s.getAll.Query() if err != nil { 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 -func (store *DefaultWordFilterStore) GetAll() (filters map[int]*WordFilter, err error) { - return store.box.Load().(map[int]*WordFilter), nil +func (s *DefaultWordFilterStore) GetAll() (filters map[int]*WordFilter, err error) { + return s.box.Load().(map[int]*WordFilter), nil } // Create adds a new word filter to the database and refreshes the memory cache -func (store *DefaultWordFilterStore) Create(find string, replacement string) error { - _, err := store.create.Exec(find, replacement) +func (s *DefaultWordFilterStore) Create(find string, replacement string) error { + _, err := s.create.Exec(find, replacement) if err != nil { return err } - return store.ReloadAll() + return s.ReloadAll() } // Delete removes a word filter from the database and refreshes the memory cache -func (store *DefaultWordFilterStore) Delete(id int) error { - _, err := store.delete.Exec(id) +func (s *DefaultWordFilterStore) Delete(id int) error { + _, err := s.delete.Exec(id) if err != nil { return err } - return store.ReloadAll() + return s.ReloadAll() } -func (store *DefaultWordFilterStore) Update(id int, find string, replacement string) error { - _, err := store.update.Exec(find, replacement, id) +func (s *DefaultWordFilterStore) Update(id int, find string, replacement string) error { + _, err := s.update.Exec(find, replacement, id) if err != nil { 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 -func (store *DefaultWordFilterStore) Length() int { - return len(store.box.Load().(map[int]*WordFilter)) +func (s *DefaultWordFilterStore) Length() int { + 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 -func (store *DefaultWordFilterStore) EstCount() int { - return len(store.box.Load().(map[int]*WordFilter)) +func (s *DefaultWordFilterStore) EstCount() int { + return len(s.box.Load().(map[int]*WordFilter)) } -// GlobalCount gets the total number of word filters directly from the database -func (store *DefaultWordFilterStore) GlobalCount() (count int) { - err := store.count.QueryRow().Scan(&count) +// Count gets the total number of word filters directly from the database +func (s *DefaultWordFilterStore) Count() (count int) { + err := s.count.QueryRow().Scan(&count) if err != nil { LogError(err) } diff --git a/main.go b/main.go index 69a99f63..1de9775e 100644 --- a/main.go +++ b/main.go @@ -243,6 +243,10 @@ func storeInit() (err error) { if err != nil { 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 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? if ucache != nil { ucap := ucache.GetCapacity() - if ucache.Length() <= ucap || c.Users.GlobalCount() <= ucap { + if ucache.Length() <= ucap || c.Users.Count() <= ucap { couldNotDealloc = false continue } diff --git a/misc_test.go b/misc_test.go index 20bf03bd..85ef51f5 100644 --- a/misc_test.go +++ b/misc_test.go @@ -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, 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 // TODO: Write tests for the registration validators @@ -452,7 +452,7 @@ func topicStoreTest(t *testing.T, newID int) { ok = c.Topics.Exists(1) 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)) //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, 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)) var iFrag = func(cond bool) string { @@ -541,7 +541,7 @@ func TestForumStore(t *testing.T) { fcache, ok := c.Forums.(c.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") _, 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, 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") forum, err = c.Forums.Get(3) @@ -618,7 +618,7 @@ func TestForumStore(t *testing.T) { expectNilErr(t, c.Forums.Delete(3)) 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, !c.Forums.Exists(3), "FID #3 shouldn't exist after being deleted") _, err = c.Forums.Get(3) @@ -938,10 +938,37 @@ func TestProfileReplyStore(t *testing.T) { // 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) { miscinit(t) 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) expectNilErr(t, err) 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) { err := store.Create("something", 0, "bumblefly", "::1", 1) expectNilErr(t, err) - count := store.GlobalCount() - expect(t, count == 1, fmt.Sprintf("store.GlobalCount should return one, not %d", count)) + count := store.Count() + expect(t, count == 1, fmt.Sprintf("store.Count should return one, not %d", count)) logs, err := store.GetOffset(0, 25) recordMustExist(t, err, "We should have at-least one "+phrase) 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 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.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() expectNilErr(t, err) // TODO: Slightly confusing that we don't get ErrNoRow here expect(t, len(filters) == 0, "Word filter map should be empty") @@ -1169,7 +1196,7 @@ func TestWordFilters(t *testing.T) { expectNilErr(t, err) 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.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() expectNilErr(t, err) expect(t, len(filters) == 1, "Word filter map should not be empty") diff --git a/plugin_heythere.go b/plugin_heythere.go index c9f75c68..04f03b6c 100644 --- a/plugin_heythere.go +++ b/plugin_heythere.go @@ -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}) } -// 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 { plugin.AddHook("topic_reply_row_assign", heythereReply) return nil diff --git a/routes/account.go b/routes/account.go index 8449a968..072000de 100644 --- a/routes/account.go +++ b/routes/account.go @@ -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 { 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")) perPage := 12 offset, page, lastPage := c.PageOffset(logCount, page, perPage) diff --git a/routes/common.go b/routes/common.go index da7c18d5..14596ef1 100644 --- a/routes/common.go +++ b/routes/common.go @@ -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 { - c.PrepResources(&header.CurrentUser, header, header.Theme) - if header.CurrentUser.Loggedin { - header.MetaDesc = "" - header.OGDesc = "" - } else if header.MetaDesc != "" && header.OGDesc == "" { - header.OGDesc = header.MetaDesc +func renderTemplate3(tmplName string, hookName string, w http.ResponseWriter, r *http.Request, h *c.Header, pi interface{}) error { + c.PrepResources(&h.CurrentUser, h, h.Theme) + if h.CurrentUser.Loggedin { + h.MetaDesc = "" + h.OGDesc = "" + } else if h.MetaDesc != "" && h.OGDesc == "" { + h.OGDesc = h.MetaDesc } - header.AddScript("global.js") - if header.CurrentUser.Loggedin { - header.AddScriptAsync("member.js") + h.AddScript("global.js") + if h.CurrentUser.Loggedin { + h.AddScriptAsync("member.js") } - FootHeaders(w, header) - if header.CurrentUser.IsAdmin { - header.Elapsed1 = time.Since(header.StartedAt).String() + FootHeaders(w, h) + if h.CurrentUser.IsAdmin { + 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 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 diff --git a/routes/forum.go b/routes/forum.go index 4b5bd1ce..3dd16c38 100644 --- a/routes/forum.go +++ b/routes/forum.go @@ -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) pi := c.ForumPage{header, topicList, forum, c.Paginator{pageList, page, lastPage}} - var tmpl = forum.Tmpl + tmpl := forum.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) return ferr } diff --git a/routes/misc.go b/routes/misc.go index b1b69fe1..01502c10 100644 --- a/routes/misc.go +++ b/routes/misc.go @@ -13,7 +13,7 @@ import ( "github.com/Azareal/Gosora/common/phrases" ) -var cacheControlMaxAge = "max-age=" + strconv.Itoa(int(c.Day)) // TODO: Make this a c.Config value +var cacheControlMaxAge = "max-age=" + strconv.Itoa(int(c.Day)) // TODO: Make this a c.Config value var cacheControlMaxAgeWeek = "max-age=" + strconv.Itoa(int(c.Week)) // TODO: Make this a c.Config value // GET functions @@ -68,15 +68,16 @@ func CustomPage(w http.ResponseWriter, r *http.Request, user c.User, header *c.H } else if err != sql.ErrNoRows { 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") + // 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 diff --git a/routes/panel/debug.go b/routes/panel/debug.go index 8b6ab7fb..a92dc207 100644 --- a/routes/panel/debug.go +++ b/routes/panel/debug.go @@ -60,6 +60,31 @@ func Debug(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError { } 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}) } diff --git a/routes/panel/logs.go b/routes/panel/logs.go index 8d0ded09..b909b6a5 100644 --- a/routes/panel/logs.go +++ b/routes/panel/logs.go @@ -18,7 +18,7 @@ func LogsRegs(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError return ferr } - logCount := c.RegLogs.GlobalCount() + logCount := c.RegLogs.Count() page, _ := strconv.Atoi(r.FormValue("page")) perPage := 10 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 } - logCount := c.ModLogs.GlobalCount() + logCount := c.ModLogs.Count() page, _ := strconv.Atoi(r.FormValue("page")) perPage := 10 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 } - logCount := c.ModLogs.GlobalCount() + logCount := c.ModLogs.Count() page, _ := strconv.Atoi(r.FormValue("page")) perPage := 10 offset, page, lastPage := c.PageOffset(logCount, page, perPage) diff --git a/routes/panel/pages.go b/routes/panel/pages.go index 54324f2c..e1a08ab1 100644 --- a/routes/panel/pages.go +++ b/routes/panel/pages.go @@ -21,7 +21,7 @@ func Pages(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError { } // TODO: Test the pagination here - pageCount := c.Pages.GlobalCount() + pageCount := c.Pages.Count() page, _ := strconv.Atoi(r.FormValue("page")) perPage := 15 offset, page, lastPage := c.PageOffset(pageCount, page, perPage) diff --git a/routes/topic.go b/routes/topic.go index 65742459..2864d1ad 100644 --- a/routes/topic.go +++ b/routes/topic.go @@ -7,6 +7,7 @@ import ( "encoding/json" "errors" "io" + //"fmt" "log" "net/http" "os" @@ -148,7 +149,18 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user c.User, header *c.He header.Zone = "view_topic" header.ZoneID = topic.ID 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.ForumViewCounter.Bump(topic.ParentID) return rerr @@ -848,11 +860,13 @@ func addTopicAction(action string, topic *c.Topic, user c.User) error { // TODO: Refactor this func LikeTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid string) c.RouteError { + //fmt.Println("i1") isJs := (r.PostFormValue("isJs") == "1") tid, err := strconv.Atoi(stid) if err != nil { return c.PreErrorJSQ(phrases.GetErrorPhrase("id_must_be_integer"), w, r, isJs) } + //fmt.Println("i2") topic, err := c.Topics.Get(tid) 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 { return c.InternalErrorJSQ(err, w, r, isJs) } + //fmt.Println("i3") // TODO: Add hooks to make use of headerLite 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 { return c.LocalErrorJSQ("You can't like your own topics", w, r, user, isJs) } + //fmt.Println("i4") _, err = c.Users.Get(topic.CreatedBy) 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 { return c.InternalErrorJSQ(err, w, r, isJs) } + //fmt.Println("i5") score := 1 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 { return c.InternalErrorJSQ(err, w, r, isJs) } + //fmt.Println("i6") // ! 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} @@ -894,11 +912,13 @@ func LikeTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid s if err != nil { return c.InternalErrorJSQ(err, w, r, isJs) } + //fmt.Println("i7") skip, rerr := lite.Hooks.VhookSkippable("action_end_like_topic", topic.ID, &user) if skip || rerr != nil { return rerr } + //fmt.Println("i8") if !isJs { http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther) diff --git a/templates/forum_gallery.html b/templates/forum_gallery.html index f23e2676..c36eb410 100644 --- a/templates/forum_gallery.html +++ b/templates/forum_gallery.html @@ -43,8 +43,8 @@ {{end}} {{end}} -
- {{range .ItemList}}
+
+ {{range .ItemList}}

{{.Title}} diff --git a/templates/panel_debug.html b/templates/panel_debug.html index f8918f98..bd329125 100644 --- a/templates/panel_debug.html +++ b/templates/panel_debug.html @@ -77,16 +77,58 @@
User Cache
Reply Cache
-
{{.TCache}} / {{.TCap}}
-
{{.UCache}} / {{.UCap}}
-
{{.RCache}} / {{.RCap}}
+
{{.Cache.Topics}} / {{.Cache.TCap}}
+
{{.Cache.Users}} / {{.Cache.UCap}}
+
{{.Cache.Replies}} / {{.Cache.RCap}}
Topic List
???
???
-
{{if .TopicListThaw}}Thawed{{else}}Sleeping{{end}}
+
{{if .Cache.TopicListThaw}}Thawed{{else}}Sleeping{{end}}
?
?
+
+
+

Database

+
+
+
Topics
+
Users
+
Replies
+ +
{{.Database.Topics}}
+
{{.Database.Users}}
+
{{.Database.Replies}}
+ + +
Profile Replies
+
Activity Stream
+
???
+ +
{{.Database.ProfileReplies}}
+
{{.Database.ActivityStream}}
+
?
+
+
+

Disk

+
+
+
Static Files
+
Attachments
+
Avatars
+ +
{{bunit .Disk.Static}}
+
{{bunit .Disk.Attachments}}
+
{{bunit .Disk.Avatars}}
+ + +
Log Files
+
Backups
+
???
+ +
{{bunit .Disk.Logs}}
+
{{bunit .Disk.Backups}}
+
?
\ No newline at end of file