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"
"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)
}

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)
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)
}

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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) {

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
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
}

View File

@ -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 {

View File

@ -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()
}

View File

@ -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
}

View File

@ -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
}

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'")
// 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)

View File

@ -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")
}

View File

@ -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)

View File

@ -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) {

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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)
}

View File

@ -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
}

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, 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")

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})
}
// 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

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 {
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)

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 {
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

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)
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
}

View File

@ -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

View File

@ -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})
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -43,8 +43,8 @@
</div>
{{end}}
{{end}}
<div id="forum_topic_list" class="rowblock micro_grid" aria-label="{{lang "forum_list_aria"}}">
{{range .ItemList}}<div class="rowitem" data-tid="{{.ID}}" style="grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));">
<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}}">
<div>
<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>

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>Reply Cache</span></div>
<div class="grid_item grid_stat"><span>{{.TCache}} / {{.TCap}}</span></div>
<div class="grid_item grid_stat"><span>{{.UCache}} / {{.UCap}}</span></div>
<div class="grid_item grid_stat"><span>{{.RCache}} / {{.RCap}}</span></div>
<div class="grid_item grid_stat"><span>{{.Cache.Topics}} / {{.Cache.TCap}}</span></div>
<div class="grid_item grid_stat"><span>{{.Cache.Users}} / {{.Cache.UCap}}</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>???</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>
<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>