Added the AboutSegment feature, you can see this in use on Cosora, it's a little raw right now, but I'm planning to polish it in the next commit.

Refactored the code to use switches instead of if blocks in some places.
Refactored the Dashboard to make it easier to add icons to it like I did with Cosora.
You can now use maps in transpiled templates.
Made progress on Cosora's footer.
Swapped out the ThemeName property in the HeaderVars struct for a more general and flexible Theme property.
Added the colstack CSS class to make it easier to style the layouts for the Control Panel and profile.
Renamed the FStore variable to Forums.
Renamed the Fpstore variable to FPStore.
Renamed the Gstore variable to Groups.
Split the MemoryTopicStore into DefaultTopicStore and MemoryTopicCache.
Split the MemoryUserStore into DefaultUserStore and MemoryUserCache.
Removed the NullUserStore, SQLUserStore, and SQLTopicStore.
Added the NullTopicCache and NullUserCache.
Moved the Reload method out of the TopicCache interface and into the TopicStore one.
Moved the Reload method out of the UserCache interface and into the UserStore one.
Added the SetCache and GetCache methods to the TopicStore and UserStore.
Added the BypassGetAll method to the WordFilterMap type.
Renamed routePanelSetting to routePanelSettingEdit.
Renamed routePanelSettingEdit to routePanelSettingEditSubmit.
Moved the page titles into the english language pack.
Split main() into main and afterDBInit to avoid code duplication in general_test.go
Added the ReqIsJson method so that we don't have to sniff the headers every time.
Added the LogStore interface.
Added the SQLModLogStore and the SQLAdminLogStore.
Refactored the phrase system to use getPhrasePlaceholder instead of hard-coding the string to return in a bunch of functions.
Removed a redundant rank check.
Added the GuildStore to plugin_guilds.
Added the about_segment_title and about_segment_body settings.
Refactored the setting system to use predefined errors to make it easier for an upstream caller to filter out sensitive error messages as opposed to safe errors.
Added the BypassGetAll method to the SettingMap type.
Added the Update method to the SettingMap type.
BulkGet is now exposed via the MemoryUserCache.
Refactored more logs in the template transpiler to reduce the amount of indentation.
Refactored the tests to take up fewer lines.
Further improved the Cosora theme's colours, padding, and profiles.
Added styling for the Control Panel Dashboard to the Cosora Theme.
Reduced the amount of code duplication in the installer query generator and opened the door to certain types of auto-migrations.
Refactored the Control Panel Dashboard to reduce the amount of code duplication.
Refactored the modlog route to reduce the amount of code duplication and string concatenation.
This commit is contained in:
Azareal 2017-11-23 05:37:08 +00:00
parent 8d0479f4b2
commit 381ce3083a
93 changed files with 1890 additions and 1631 deletions

View File

@ -192,6 +192,8 @@ More images in the /images/ folder. Beware though, some of them are *really* out
* ithub.com/denisenkom/go-mssqldb For interfacing with MSSQL. You will be able to pick this instead of MSSQL soon. * ithub.com/denisenkom/go-mssqldb For interfacing with MSSQL. You will be able to pick this instead of MSSQL soon.
* github.com/go-ego/riot A search engine library.
# Bundled Plugins # Bundled Plugins
There are several plugins which are bundled with the software by default. These cover various common tasks which aren't common enough to clutter the core with or which have competing implementation methods (E.g. plugin_markdown vs plugin_bbcode for post mark-up). There are several plugins which are bundled with the software by default. These cover various common tasks which aren't common enough to clutter the core with or which have competing implementation methods (E.g. plugin_markdown vs plugin_bbcode for post mark-up).

View File

@ -6,31 +6,64 @@ import (
"../query_gen/lib" "../query_gen/lib"
) )
type LogStmts struct { var ModLogs LogStore
addModLogEntry *sql.Stmt var AdminLogs LogStore
addAdminLogEntry *sql.Stmt
type LogStore interface {
Create(action string, elementID int, elementType string, ipaddress string, actorID int) (err error)
GlobalCount() int
} }
var logStmts LogStmts type SQLModLogStore struct {
create *sql.Stmt
func init() { count *sql.Stmt
DbInits.Add(func(acc *qgen.Accumulator) error {
logStmts = LogStmts{
addModLogEntry: acc.Insert("moderation_logs").Columns("action, elementID, elementType, ipaddress, actorID, doneAt").Fields("?,?,?,?,?,UTC_TIMESTAMP()").Prepare(),
addAdminLogEntry: acc.Insert("administration_logs").Columns("action, elementID, elementType, ipaddress, actorID, doneAt").Fields("?,?,?,?,?,UTC_TIMESTAMP()").Prepare(),
} }
return acc.FirstError()
}) func NewModLogStore() (*SQLModLogStore, error) {
acc := qgen.Builder.Accumulator()
return &SQLModLogStore{
create: acc.Insert("moderation_logs").Columns("action, elementID, elementType, ipaddress, actorID, doneAt").Fields("?,?,?,?,?,UTC_TIMESTAMP()").Prepare(),
count: acc.Count("moderation_logs").Prepare(),
}, acc.FirstError()
} }
// TODO: Make a store for this? // TODO: Make a store for this?
func AddModLog(action string, elementID int, elementType string, ipaddress string, actorID int) (err error) { func (store *SQLModLogStore) Create(action string, elementID int, elementType string, ipaddress string, actorID int) (err error) {
_, err = logStmts.addModLogEntry.Exec(action, elementID, elementType, ipaddress, actorID) _, err = store.create.Exec(action, elementID, elementType, ipaddress, actorID)
return err return err
} }
func (store *SQLModLogStore) GlobalCount() (logCount int) {
err := store.count.QueryRow().Scan(&logCount)
if err != nil {
LogError(err)
}
return logCount
}
type SQLAdminLogStore struct {
create *sql.Stmt
count *sql.Stmt
}
func NewAdminLogStore() (*SQLAdminLogStore, error) {
acc := qgen.Builder.Accumulator()
return &SQLAdminLogStore{
create: acc.Insert("administration_logs").Columns("action, elementID, elementType, ipaddress, actorID, doneAt").Fields("?,?,?,?,?,UTC_TIMESTAMP()").Prepare(),
count: acc.Count("administration_logs").Prepare(),
}, acc.FirstError()
}
// TODO: Make a store for this? // TODO: Make a store for this?
func AddAdminLog(action string, elementID string, elementType int, ipaddress string, actorID int) (err error) { func (store *SQLAdminLogStore) Create(action string, elementID int, elementType string, ipaddress string, actorID int) (err error) {
_, err = logStmts.addAdminLogEntry.Exec(action, elementID, elementType, ipaddress, actorID) _, err = store.create.Exec(action, elementID, elementType, ipaddress, actorID)
return err return err
} }
func (store *SQLAdminLogStore) GlobalCount() (logCount int) {
err := store.count.QueryRow().Scan(&logCount)
if err != nil {
LogError(err)
}
return logCount
}

View File

@ -91,9 +91,9 @@ func (auth *DefaultAuth) ForceLogout(uid int) error {
} }
// Flush the user out of the cache // Flush the user out of the cache
ucache, ok := Users.(UserCache) ucache := Users.GetCache()
if ok { if ucache != nil {
ucache.CacheRemove(uid) ucache.Remove(uid)
} }
return nil return nil
@ -170,9 +170,9 @@ func (auth *DefaultAuth) CreateSession(uid int) (session string, err error) {
} }
// Flush the user data from the cache // Flush the user data from the cache
ucache, ok := Users.(UserCache) ucache := Users.GetCache()
if ok { if ucache != nil {
ucache.CacheRemove(uid) ucache.Remove(uid)
} }
return session, nil return session, nil
} }

View File

@ -87,7 +87,7 @@ func (forum *Forum) Update(name string, desc string, active bool, preset string)
return err return err
} }
} }
_ = Fstore.Reload(forum.ID) _ = Forums.Reload(forum.ID)
return nil return nil
} }
@ -113,11 +113,11 @@ func (forum *Forum) setPreset(fperms *ForumPerms, preset string, gid int) (err e
LogError(err) LogError(err)
return errors.New("Unable to update the forum") return errors.New("Unable to update the forum")
} }
err = Fstore.Reload(forum.ID) err = Forums.Reload(forum.ID)
if err != nil { if err != nil {
return errors.New("Unable to reload forum") return errors.New("Unable to reload forum")
} }
err = Fpstore.ReloadGroup(forum.ID, gid) err = FPStore.ReloadGroup(forum.ID, gid)
if err != nil { if err != nil {
return errors.New("Unable to reload the forum permissions") return errors.New("Unable to reload the forum permissions")
} }

View File

@ -22,6 +22,7 @@ var LocalPermList = []string{
"CloseTopic", "CloseTopic",
} }
// TODO: Rename this to ForumPermSet?
/* Inherit from group permissions for ones we don't have */ /* Inherit from group permissions for ones we don't have */
type ForumPerms struct { type ForumPerms struct {
ViewTopic bool ViewTopic bool
@ -165,7 +166,7 @@ func PermmapToQuery(permmap map[string]*ForumPerms, fid int) error {
if err != nil { if err != nil {
return err return err
} }
return Fpstore.Reload(fid) return FPStore.Reload(fid)
} }
func ReplaceForumPermsForGroup(gid int, presetSet map[int]string, permSets map[int]*ForumPerms) error { func ReplaceForumPermsForGroup(gid int, presetSet map[int]string, permSets map[int]*ForumPerms) error {

View File

@ -9,7 +9,7 @@ import (
"../query_gen/lib" "../query_gen/lib"
) )
var Fpstore ForumPermsStore var FPStore ForumPermsStore
type ForumPermsStore interface { type ForumPermsStore interface {
Init() error Init() error
@ -41,7 +41,7 @@ func NewMemoryForumPermsStore() (*MemoryForumPermsStore, error) {
func (fps *MemoryForumPermsStore) Init() error { func (fps *MemoryForumPermsStore) Init() error {
fps.updateMutex.Lock() fps.updateMutex.Lock()
defer fps.updateMutex.Unlock() defer fps.updateMutex.Unlock()
fids, err := Fstore.GetAllIDs() fids, err := Forums.GetAllIDs()
if err != nil { if err != nil {
return err return err
} }
@ -55,7 +55,6 @@ func (fps *MemoryForumPermsStore) Init() error {
debugLog("Adding the forum permissions") debugLog("Adding the forum permissions")
debugDetail("forumPerms[gid][fid]") debugDetail("forumPerms[gid][fid]")
// Temporarily store the forum perms in a map before transferring it to a much faster and thread-safe slice
forumPerms = make(map[int]map[int]*ForumPerms) forumPerms = make(map[int]map[int]*ForumPerms)
for rows.Next() { for rows.Next() {
var gid, fid int var gid, fid int
@ -97,7 +96,7 @@ func (fps *MemoryForumPermsStore) Reload(fid int) error {
fps.updateMutex.Lock() fps.updateMutex.Lock()
defer fps.updateMutex.Unlock() defer fps.updateMutex.Unlock()
debugLogf("Reloading the forum permissions for forum #%d", fid) debugLogf("Reloading the forum permissions for forum #%d", fid)
fids, err := Fstore.GetAllIDs() fids, err := Forums.GetAllIDs()
if err != nil { if err != nil {
return err return err
} }
@ -143,7 +142,7 @@ func (fps *MemoryForumPermsStore) ReloadGroup(fid int, gid int) (err error) {
if err != nil { if err != nil {
return err return err
} }
group, err := Gstore.Get(gid) group, err := Groups.Get(gid)
if err != nil { if err != nil {
return err return err
} }
@ -153,7 +152,7 @@ func (fps *MemoryForumPermsStore) ReloadGroup(fid int, gid int) (err error) {
} }
func (fps *MemoryForumPermsStore) cascadePermSetToGroups(forumPerms map[int]map[int]*ForumPerms, fids []int) error { func (fps *MemoryForumPermsStore) cascadePermSetToGroups(forumPerms map[int]map[int]*ForumPerms, fids []int) error {
groups, err := Gstore.GetAll() groups, err := Groups.GetAll()
if err != nil { if err != nil {
return err return err
} }
@ -200,7 +199,7 @@ func (fps *MemoryForumPermsStore) cascadePermSetToGroup(forumPerms map[int]map[i
// TODO: Add a hook here and have plugin_guilds use it // TODO: Add a hook here and have plugin_guilds use it
func (fps *MemoryForumPermsStore) Get(fid int, gid int) (fperms *ForumPerms, err error) { func (fps *MemoryForumPermsStore) Get(fid int, gid int) (fperms *ForumPerms, err error) {
group, err := Gstore.Get(gid) group, err := Groups.Get(gid)
if err != nil { if err != nil {
return fperms, ErrNoRows return fperms, ErrNoRows
} }

View File

@ -19,7 +19,7 @@ import (
var forumCreateMutex sync.Mutex var forumCreateMutex sync.Mutex
var forumPerms map[int]map[int]*ForumPerms // [gid][fid]*ForumPerms // TODO: Add an abstraction around this and make it more thread-safe var forumPerms map[int]map[int]*ForumPerms // [gid][fid]*ForumPerms // TODO: Add an abstraction around this and make it more thread-safe
var Fstore ForumStore var Forums ForumStore
// ForumStore is an interface for accessing the forums and the metadata stored on them // ForumStore is an interface for accessing the forums and the metadata stored on them
type ForumStore interface { type ForumStore interface {

View File

@ -51,7 +51,7 @@ func (group *Group) ChangeRank(isAdmin bool, isMod bool, isBanned bool) (err err
return err return err
} }
Gstore.Reload(group.ID) Groups.Reload(group.ID)
return nil return nil
} }

View File

@ -12,7 +12,7 @@ import (
"../query_gen/lib" "../query_gen/lib"
) )
var Gstore GroupStore var Groups GroupStore
// ? - We could fallback onto the database when an item can't be found in the cache? // ? - We could fallback onto the database when an item can't be found in the cache?
type GroupStore interface { type GroupStore interface {
@ -230,7 +230,7 @@ func (mgs *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod
} }
// Generate the forum permissions based on the presets... // Generate the forum permissions based on the presets...
fdata, err := Fstore.GetAll() fdata, err := Forums.GetAll()
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -279,7 +279,7 @@ func (mgs *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod
mgs.Unlock() mgs.Unlock()
for _, forum := range fdata { for _, forum := range fdata {
err = Fpstore.Reload(forum.ID) err = FPStore.Reload(forum.ID)
if err != nil { if err != nil {
return gid, err return gid, err
} }

View File

@ -0,0 +1,55 @@
package common
type NullTopicCache struct {
}
// NewNullTopicCache gives you a new instance of NullTopicCache
func NewNullTopicCache() *NullTopicCache {
return &NullTopicCache{}
}
func (mts *NullTopicCache) Get(id int) (*Topic, error) {
return nil, ErrNoRows
}
func (mts *NullTopicCache) GetUnsafe(id int) (*Topic, error) {
return nil, ErrNoRows
}
func (mts *NullTopicCache) Set(_ *Topic) error {
return nil
}
func (mts *NullTopicCache) Add(item *Topic) error {
_ = item
return nil
}
// TODO: Make these length increments thread-safe. Ditto for the other DataStores
func (mts *NullTopicCache) AddUnsafe(item *Topic) error {
_ = item
return nil
}
// TODO: Make these length decrements thread-safe. Ditto for the other DataStores
func (mts *NullTopicCache) Remove(id int) error {
return nil
}
func (mts *NullTopicCache) RemoveUnsafe(id int) error {
return nil
}
func (mts *NullTopicCache) Flush() {
}
func (mts *NullTopicCache) Length() int {
return 0
}
func (mts *NullTopicCache) SetCapacity(_ int) {
}
func (mts *NullTopicCache) GetCapacity() int {
return 0
}

57
common/null_user_cache.go Normal file
View File

@ -0,0 +1,57 @@
package common
type NullUserCache struct {
}
// NewNullUserCache gives you a new instance of NullUserCache
func NewNullUserCache() *NullUserCache {
return &NullUserCache{}
}
func (mus *NullUserCache) Get(id int) (*User, error) {
return nil, ErrNoRows
}
func (mus *NullUserCache) BulkGet(ids []int) (list []*User) {
return list
}
func (mus *NullUserCache) GetUnsafe(id int) (*User, error) {
return nil, ErrNoRows
}
func (mus *NullUserCache) Set(_ *User) error {
return nil
}
func (mus *NullUserCache) Add(item *User) error {
_ = item
return nil
}
func (mus *NullUserCache) AddUnsafe(item *User) error {
_ = item
return nil
}
func (mus *NullUserCache) Remove(id int) error {
return nil
}
func (mus *NullUserCache) RemoveUnsafe(id int) error {
return nil
}
func (mus *NullUserCache) Flush() {
}
func (mus *NullUserCache) Length() int {
return 0
}
func (mus *NullUserCache) SetCapacity(_ int) {
}
func (mus *NullUserCache) GetCapacity() int {
return 0
}

View File

@ -13,8 +13,8 @@ type HeaderVars struct {
Widgets PageWidgets Widgets PageWidgets
Site *site Site *site
Settings SettingMap Settings SettingMap
Themes map[string]Theme // TODO: Use a slice containing every theme instead of the main map for speed Themes map[string]Theme // TODO: Use a slice containing every theme instead of the main map for speed?
ThemeName string Theme Theme
//TemplateName string // TODO: Use this to move template calls to the router rather than duplicating them over and over and over? //TemplateName string // TODO: Use this to move template calls to the router rather than duplicating them over and over and over?
ExtData ExtData ExtData ExtData
} }
@ -282,5 +282,5 @@ type AreYouSure struct {
// This is mostly for errors.go, please create *HeaderVars on the spot instead of relying on this or the atomic store underlying it, if possible // This is mostly for errors.go, please create *HeaderVars on the spot instead of relying on this or the atomic store underlying it, if possible
// TODO: Write a test for this // TODO: Write a test for this
func DefaultHeaderVar() *HeaderVars { func DefaultHeaderVar() *HeaderVars {
return &HeaderVars{Site: Site, ThemeName: fallbackTheme} return &HeaderVars{Site: Site, Theme: Themes[fallbackTheme]}
} }

View File

@ -219,7 +219,7 @@ func ParseMessage(msg string, sectionID int, sectionType string /*, user User*/)
i += intLen i += intLen
topic, err := Topics.Get(tid) topic, err := Topics.Get(tid)
if err != nil || !Fstore.Exists(topic.ParentID) { if err != nil || !Forums.Exists(topic.ParentID) {
outbytes = append(outbytes, InvalidTopic...) outbytes = append(outbytes, InvalidTopic...)
lastItem = i lastItem = i
continue continue
@ -250,7 +250,7 @@ func ParseMessage(msg string, sectionID int, sectionType string /*, user User*/)
reply := BlankReply() reply := BlankReply()
reply.ID = rid reply.ID = rid
topic, err := reply.Topic() topic, err := reply.Topic()
if err != nil || !Fstore.Exists(topic.ParentID) { if err != nil || !Forums.Exists(topic.ParentID) {
outbytes = append(outbytes, InvalidTopic...) outbytes = append(outbytes, InvalidTopic...)
lastItem = i lastItem = i
continue continue
@ -271,7 +271,7 @@ func ParseMessage(msg string, sectionID int, sectionType string /*, user User*/)
fid, intLen := CoerceIntBytes(msgbytes[start:]) fid, intLen := CoerceIntBytes(msgbytes[start:])
i += intLen i += intLen
if !Fstore.Exists(fid) { if !Forums.Exists(fid) {
outbytes = append(outbytes, InvalidForum...) outbytes = append(outbytes, InvalidForum...)
lastItem = i lastItem = i
continue continue

View File

@ -189,7 +189,7 @@ func RebuildGroupPermissions(gid int) error {
return err return err
} }
group, err := Gstore.Get(gid) group, err := Groups.Get(gid)
if err != nil { if err != nil {
return err return err
} }

View File

@ -17,6 +17,7 @@ import (
"sync/atomic" "sync/atomic"
) )
// TODO: Add a phrase store?
// TODO: Let the admin edit phrases from inside the Control Panel? How should we persist these? Should we create a copy of the langpack or edit the primaries? Use the changeLangpack mutex for this? // TODO: Let the admin edit phrases from inside the Control Panel? How should we persist these? Should we create a copy of the langpack or edit the primaries? Use the changeLangpack mutex for this?
// nolint Be quiet megacheck, this *is* used // nolint Be quiet megacheck, this *is* used
var currentLangPack atomic.Value var currentLangPack atomic.Value
@ -41,6 +42,8 @@ type LanguagePack struct {
SettingLabels map[string]string SettingLabels map[string]string
PermPresets map[string]string PermPresets map[string]string
Accounts map[string]string // TODO: Apply these phrases in the software proper Accounts map[string]string // TODO: Apply these phrases in the software proper
Errors map[string]map[string]string // map[category]map[name]value
PageTitles map[string]string
} }
// TODO: Add the ability to edit language JSON files from the Control Panel and automatically scan the files for changes // TODO: Add the ability to edit language JSON files from the Control Panel and automatically scan the files for changes
@ -114,7 +117,7 @@ func GetPhrase(name string) (string, bool) {
func GetGlobalPermPhrase(name string) string { func GetGlobalPermPhrase(name string) string {
res, ok := currentLangPack.Load().(*LanguagePack).GlobalPerms[name] res, ok := currentLangPack.Load().(*LanguagePack).GlobalPerms[name]
if !ok { if !ok {
return "{name}" return getPhrasePlaceholder()
} }
return res return res
} }
@ -122,7 +125,7 @@ func GetGlobalPermPhrase(name string) string {
func GetLocalPermPhrase(name string) string { func GetLocalPermPhrase(name string) string {
res, ok := currentLangPack.Load().(*LanguagePack).LocalPerms[name] res, ok := currentLangPack.Load().(*LanguagePack).LocalPerms[name]
if !ok { if !ok {
return "{name}" return getPhrasePlaceholder()
} }
return res return res
} }
@ -130,7 +133,7 @@ func GetLocalPermPhrase(name string) string {
func GetSettingLabel(name string) string { func GetSettingLabel(name string) string {
res, ok := currentLangPack.Load().(*LanguagePack).SettingLabels[name] res, ok := currentLangPack.Load().(*LanguagePack).SettingLabels[name]
if !ok { if !ok {
return "{name}" return getPhrasePlaceholder()
} }
return res return res
} }
@ -146,11 +149,32 @@ func GetAllPermPresets() map[string]string {
func GetAccountPhrase(name string) string { func GetAccountPhrase(name string) string {
res, ok := currentLangPack.Load().(*LanguagePack).Accounts[name] res, ok := currentLangPack.Load().(*LanguagePack).Accounts[name]
if !ok { if !ok {
return "{name}" return getPhrasePlaceholder()
} }
return res return res
} }
// TODO: Does comma ok work with multi-dimensional maps?
func GetErrorPhrase(category string, name string) string {
res, ok := currentLangPack.Load().(*LanguagePack).Errors[category][name]
if !ok {
return getPhrasePlaceholder()
}
return res
}
func GetTitlePhrase(name string) string {
res, ok := currentLangPack.Load().(*LanguagePack).PageTitles[name]
if !ok {
return getPhrasePlaceholder()
}
return res
}
func getPhrasePlaceholder() string {
return "{name}"
}
// ? - Use runtime reflection for updating phrases? // ? - Use runtime reflection for updating phrases?
// TODO: Implement these // TODO: Implement these
func AddPhrase() { func AddPhrase() {

View File

@ -112,9 +112,9 @@ func (reply *Reply) Delete() error {
} }
// TODO: Move this bit to *Topic // TODO: Move this bit to *Topic
_, err = replyStmts.removeRepliesFromTopic.Exec(1, reply.ParentID) _, err = replyStmts.removeRepliesFromTopic.Exec(1, reply.ParentID)
tcache, ok := Topics.(TopicCache) tcache := Topics.GetCache()
if ok { if tcache != nil {
tcache.CacheRemove(reply.ParentID) tcache.Remove(reply.ParentID)
} }
return err return err
} }

View File

@ -31,8 +31,7 @@ func BuildWidgets(zone string, data interface{}, headerVars *HeaderVars, r *http
} }
} }
//log.Print("Themes[headerVars.ThemeName].Sidebars", Themes[headerVars.ThemeName].Sidebars) if Themes[headerVars.Theme.Name].Sidebars == "right" {
if Themes[headerVars.ThemeName].Sidebars == "right" {
if len(Docks.RightSidebar) != 0 { if len(Docks.RightSidebar) != 0 {
var sbody string var sbody string
for _, widget := range Docks.RightSidebar { for _, widget := range Docks.RightSidebar {
@ -48,7 +47,7 @@ func BuildWidgets(zone string, data interface{}, headerVars *HeaderVars, r *http
} }
func simpleForumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *HeaderLite, rerr RouteError) { func simpleForumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *HeaderLite, rerr RouteError) {
if !Fstore.Exists(fid) { if !Forums.Exists(fid) {
return nil, PreError("The target forum doesn't exist.", w, r) return nil, PreError("The target forum doesn't exist.", w, r)
} }
@ -61,7 +60,7 @@ func simpleForumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fi
} }
} }
fperms, err := Fpstore.Get(fid, user.Group) fperms, err := FPStore.Get(fid, user.Group)
if err != nil { if err != nil {
// TODO: Refactor this // TODO: Refactor this
log.Printf("Unable to get the forum perms for Group #%d for User #%d", user.Group, user.ID) log.Printf("Unable to get the forum perms for Group #%d for User #%d", user.Group, user.ID)
@ -76,7 +75,7 @@ func forumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int)
if rerr != nil { if rerr != nil {
return headerVars, rerr return headerVars, rerr
} }
if !Fstore.Exists(fid) { if !Forums.Exists(fid) {
return headerVars, NotFound(w, r) return headerVars, NotFound(w, r)
} }
@ -88,14 +87,12 @@ func forumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int)
} }
} }
fperms, err := Fpstore.Get(fid, user.Group) fperms, err := FPStore.Get(fid, user.Group)
if err != nil { if err != nil {
// TODO: Refactor this // TODO: Refactor this
log.Printf("Unable to get the forum perms for Group #%d for User #%d", user.Group, user.ID) log.Printf("Unable to get the forum perms for Group #%d for User #%d", user.Group, user.ID)
return nil, PreError("Something weird happened", w, r) return nil, PreError("Something weird happened", w, r)
} }
//log.Printf("user.Perms: %+v\n", user.Perms)
//log.Printf("fperms: %+v\n", fperms)
cascadeForumPerms(fperms, user) cascadeForumPerms(fperms, user)
return headerVars, rerr return headerVars, rerr
} }
@ -125,28 +122,30 @@ func cascadeForumPerms(fperms *ForumPerms, user *User) {
// Even if they have the right permissions, the control panel is only open to supermods+. There are many areas without subpermissions which assume that the current user is a supermod+ and admins are extremely unlikely to give these permissions to someone who isn't at-least a supermod to begin with // Even if they have the right permissions, the control panel is only open to supermods+. There are many areas without subpermissions which assume that the current user is a supermod+ and admins are extremely unlikely to give these permissions to someone who isn't at-least a supermod to begin with
// TODO: Do a panel specific theme? // TODO: Do a panel specific theme?
func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, stats PanelStats, rerr RouteError) { func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, stats PanelStats, rerr RouteError) {
var themeName = DefaultThemeBox.Load().(string) var theme Theme
cookie, err := r.Cookie("current_theme") cookie, err := r.Cookie("current_theme")
if err == nil { if err == nil {
cookie := html.EscapeString(cookie.Value) inTheme, ok := Themes[html.EscapeString(cookie.Value)]
theme, ok := Themes[cookie]
if ok && !theme.HideFromThemes { if ok && !theme.HideFromThemes {
themeName = cookie theme = inTheme
} }
} }
if theme.Name == "" {
theme = Themes[DefaultThemeBox.Load().(string)]
}
headerVars = &HeaderVars{ headerVars = &HeaderVars{
Site: Site, Site: Site,
Settings: SettingBox.Load().(SettingMap), Settings: SettingBox.Load().(SettingMap),
Themes: Themes, Themes: Themes,
ThemeName: themeName, Theme: theme,
} }
// TODO: We should probably initialise headerVars.ExtData // TODO: We should probably initialise headerVars.ExtData
headerVars.Stylesheets = append(headerVars.Stylesheets, headerVars.ThemeName+"/panel.css") headerVars.Stylesheets = append(headerVars.Stylesheets, theme.Name+"/panel.css")
if len(Themes[headerVars.ThemeName].Resources) > 0 { if len(theme.Resources) > 0 {
rlist := Themes[headerVars.ThemeName].Resources rlist := theme.Resources
for _, resource := range rlist { for _, resource := range rlist {
if resource.Location == "global" || resource.Location == "panel" { if resource.Location == "global" || resource.Location == "panel" {
extarr := strings.Split(resource.Name, ".") extarr := strings.Split(resource.Name, ".")
@ -161,8 +160,8 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerV
} }
stats.Users = Users.GlobalCount() stats.Users = Users.GlobalCount()
stats.Groups = Gstore.GlobalCount() stats.Groups = Groups.GlobalCount()
stats.Forums = Fstore.GlobalCount() // TODO: Stop it from showing the blanked forums stats.Forums = Forums.GlobalCount() // TODO: Stop it from showing the blanked forums
stats.Settings = len(headerVars.Settings) stats.Settings = len(headerVars.Settings)
stats.WordFilters = len(WordFilterBox.Load().(WordFilterMap)) stats.WordFilters = len(WordFilterBox.Load().(WordFilterMap))
stats.Themes = len(Themes) stats.Themes = len(Themes)
@ -170,12 +169,17 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerV
pusher, ok := w.(http.Pusher) pusher, ok := w.(http.Pusher)
if ok { if ok {
pusher.Push("/static/"+headerVars.ThemeName+"/main.css", nil) pusher.Push("/static/"+theme.Name+"/main.css", nil)
pusher.Push("/static/"+headerVars.ThemeName+"/panel.css", nil) pusher.Push("/static/"+theme.Name+"/panel.css", nil)
pusher.Push("/static/global.js", nil) pusher.Push("/static/global.js", nil)
pusher.Push("/static/jquery-3.1.1.min.js", nil) pusher.Push("/static/jquery-3.1.1.min.js", nil)
// TODO: Push the theme CSS files // TODO: Test these
// TODO: Push the theme scripts for _, sheet := range headerVars.Stylesheets {
pusher.Push("/static/"+sheet, nil)
}
for _, script := range headerVars.Scripts {
pusher.Push("/static/"+script, nil)
}
// TODO: Push avatars? // TODO: Push avatars?
} }
@ -209,30 +213,32 @@ func simpleUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header
// TODO: Add the ability for admins to restrict certain themes to certain groups? // TODO: Add the ability for admins to restrict certain themes to certain groups?
func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, rerr RouteError) { func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, rerr RouteError) {
var themeName = DefaultThemeBox.Load().(string) var theme Theme
cookie, err := r.Cookie("current_theme") cookie, err := r.Cookie("current_theme")
if err == nil { if err == nil {
cookie := html.EscapeString(cookie.Value) inTheme, ok := Themes[html.EscapeString(cookie.Value)]
theme, ok := Themes[cookie]
if ok && !theme.HideFromThemes { if ok && !theme.HideFromThemes {
themeName = cookie theme = inTheme
} }
} }
if theme.Name == "" {
theme = Themes[DefaultThemeBox.Load().(string)]
}
headerVars = &HeaderVars{ headerVars = &HeaderVars{
Site: Site, Site: Site,
Settings: SettingBox.Load().(SettingMap), Settings: SettingBox.Load().(SettingMap),
Themes: Themes, Themes: Themes,
ThemeName: themeName, Theme: theme,
} }
if user.IsBanned { if user.IsBanned {
headerVars.NoticeList = append(headerVars.NoticeList, "Your account has been suspended. Some of your permissions may have been revoked.") headerVars.NoticeList = append(headerVars.NoticeList, "Your account has been suspended. Some of your permissions may have been revoked.")
} }
if len(Themes[headerVars.ThemeName].Resources) > 0 { if len(theme.Resources) > 0 {
rlist := Themes[headerVars.ThemeName].Resources rlist := theme.Resources
for _, resource := range rlist { for _, resource := range rlist {
if resource.Location == "global" || resource.Location == "frontend" { if resource.Location == "global" || resource.Location == "frontend" {
extarr := strings.Split(resource.Name, ".") extarr := strings.Split(resource.Name, ".")
@ -248,11 +254,16 @@ func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *
pusher, ok := w.(http.Pusher) pusher, ok := w.(http.Pusher)
if ok { if ok {
pusher.Push("/static/"+headerVars.ThemeName+"/main.css", nil) pusher.Push("/static/"+theme.Name+"/main.css", nil)
pusher.Push("/static/global.js", nil) pusher.Push("/static/global.js", nil)
pusher.Push("/static/jquery-3.1.1.min.js", nil) pusher.Push("/static/jquery-3.1.1.min.js", nil)
// TODO: Push the theme CSS files // TODO: Test these
// TODO: Push the theme scripts for _, sheet := range headerVars.Stylesheets {
pusher.Push("/static/"+sheet, nil)
}
for _, script := range headerVars.Scripts {
pusher.Push("/static/"+script, nil)
}
// TODO: Push avatars? // TODO: Push avatars?
} }
@ -344,3 +355,7 @@ func NoSessionMismatch(w http.ResponseWriter, r *http.Request, user User) RouteE
} }
return nil return nil
} }
func ReqIsJson(r *http.Request) bool {
return r.Header.Get("Content-type") == "application/json"
}

View File

@ -2,6 +2,7 @@ package common
import ( import (
"database/sql" "database/sql"
"errors"
"strconv" "strconv"
"strings" "strings"
"sync/atomic" "sync/atomic"
@ -17,6 +18,7 @@ type SettingMap map[string]interface{}
type SettingStore interface { type SettingStore interface {
ParseSetting(sname string, scontent string, stype string, sconstraint string) string ParseSetting(sname string, scontent string, stype string, sconstraint string) string
BypassGet(name string) (*Setting, error) BypassGet(name string) (*Setting, error)
BypassGetAll(name string) ([]*Setting, error)
} }
type OptionLabel struct { type OptionLabel struct {
@ -35,6 +37,7 @@ type Setting struct {
type SettingStmts struct { type SettingStmts struct {
getAll *sql.Stmt getAll *sql.Stmt
get *sql.Stmt get *sql.Stmt
update *sql.Stmt
} }
var settingStmts SettingStmts var settingStmts SettingStmts
@ -45,80 +48,83 @@ func init() {
settingStmts = SettingStmts{ settingStmts = SettingStmts{
getAll: acc.Select("settings").Columns("name, content, type, constraints").Prepare(), getAll: acc.Select("settings").Columns("name, content, type, constraints").Prepare(),
get: acc.Select("settings").Columns("content, type, constraints").Where("name = ?").Prepare(), get: acc.Select("settings").Columns("content, type, constraints").Where("name = ?").Prepare(),
update: acc.Update("settings").Set("content = ?").Where("name = ?").Prepare(),
} }
return acc.FirstError() return acc.FirstError()
}) })
} }
func LoadSettings() error { func LoadSettings() error {
rows, err := settingStmts.getAll.Query()
if err != nil {
return err
}
defer rows.Close()
var sBox = SettingMap(make(map[string]interface{})) var sBox = SettingMap(make(map[string]interface{}))
var sname, scontent, stype, sconstraints string settings, err := sBox.BypassGetAll()
for rows.Next() {
err = rows.Scan(&sname, &scontent, &stype, &sconstraints)
if err != nil { if err != nil {
return err return err
} }
errmsg := sBox.ParseSetting(sname, scontent, stype, sconstraints)
if errmsg != "" { for _, setting := range settings {
return err err = sBox.ParseSetting(setting.Name, setting.Content, setting.Type, setting.Constraint)
}
}
err = rows.Err()
if err != nil { if err != nil {
return err return err
} }
}
SettingBox.Store(sBox) SettingBox.Store(sBox)
return nil return nil
} }
// nolint
var ErrNotInteger = errors.New("You were supposed to enter an integer x.x")
var ErrSettingNotInteger = errors.New("Only integers are allowed in this setting x.x")
var ErrBadConstraintNotInteger = errors.New("Invalid contraint! The constraint field wasn't an integer!")
var ErrBadSettingRange = errors.New("Only integers between a certain range are allowed in this setting")
// To avoid leaking internal state to the user
// TODO: We need to add some sort of DualError interface
func SafeSettingError(err error) bool {
return err == ErrNotInteger || err == ErrSettingNotInteger || err == ErrBadConstraintNotInteger || err == ErrBadSettingRange || err == ErrNoRows
}
// TODO: Add better support for HTML attributes (html-attribute). E.g. Meta descriptions. // TODO: Add better support for HTML attributes (html-attribute). E.g. Meta descriptions.
func (sBox SettingMap) ParseSetting(sname string, scontent string, stype string, constraint string) string { func (sBox SettingMap) ParseSetting(sname string, scontent string, stype string, constraint string) (err error) {
var err error
var ssBox = map[string]interface{}(sBox) var ssBox = map[string]interface{}(sBox)
if stype == "bool" { switch stype {
case "bool":
ssBox[sname] = (scontent == "1") ssBox[sname] = (scontent == "1")
} else if stype == "int" { case "int":
ssBox[sname], err = strconv.Atoi(scontent) ssBox[sname], err = strconv.Atoi(scontent)
if err != nil { if err != nil {
return "You were supposed to enter an integer x.x\nType mismatch in " + sname return ErrNotInteger
} }
} else if stype == "int64" { case "int64":
ssBox[sname], err = strconv.ParseInt(scontent, 10, 64) ssBox[sname], err = strconv.ParseInt(scontent, 10, 64)
if err != nil { if err != nil {
return "You were supposed to enter an integer x.x\nType mismatch in " + sname return ErrNotInteger
} }
} else if stype == "list" { case "list":
cons := strings.Split(constraint, "-") cons := strings.Split(constraint, "-")
if len(cons) < 2 { if len(cons) < 2 {
return "Invalid constraint! The second field wasn't set!" return errors.New("Invalid constraint! The second field wasn't set!")
} }
con1, err := strconv.Atoi(cons[0]) con1, err := strconv.Atoi(cons[0])
con2, err2 := strconv.Atoi(cons[1]) con2, err2 := strconv.Atoi(cons[1])
if err != nil || err2 != nil { if err != nil || err2 != nil {
return "Invalid contraint! The constraint field wasn't an integer!" return ErrBadConstraintNotInteger
} }
value, err := strconv.Atoi(scontent) value, err := strconv.Atoi(scontent)
if err != nil { if err != nil {
return "Only integers are allowed in this setting x.x\nType mismatch in " + sname return ErrSettingNotInteger
} }
if value < con1 || value > con2 { if value < con1 || value > con2 {
return "Only integers between a certain range are allowed in this setting" return ErrBadSettingRange
} }
ssBox[sname] = value ssBox[sname] = value
} else { default:
ssBox[sname] = scontent ssBox[sname] = scontent
} }
return "" return nil
} }
func (sBox SettingMap) BypassGet(name string) (*Setting, error) { func (sBox SettingMap) BypassGet(name string) (*Setting, error) {
@ -126,3 +132,51 @@ func (sBox SettingMap) BypassGet(name string) (*Setting, error) {
err := settingStmts.get.QueryRow(name).Scan(&setting.Content, &setting.Type, &setting.Constraint) err := settingStmts.get.QueryRow(name).Scan(&setting.Content, &setting.Type, &setting.Constraint)
return setting, err return setting, err
} }
func (sBox SettingMap) BypassGetAll() (settingList []*Setting, err error) {
rows, err := settingStmts.getAll.Query()
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
setting := &Setting{Name: ""}
err := rows.Scan(&setting.Name, &setting.Content, &setting.Type, &setting.Constraint)
if err != nil {
return nil, err
}
settingList = append(settingList, setting)
}
return settingList, rows.Err()
}
func (sBox SettingMap) Update(name string, content string) error {
setting, err := sBox.BypassGet(name)
if err == ErrNoRows {
return err
}
// TODO: Why is this here and not in a common function?
if setting.Type == "bool" {
if content == "on" || content == "1" {
content = "1"
} else {
content = "0"
}
}
// TODO: Make this a method or function?
_, err = settingStmts.update.Exec(content, name)
if err != nil {
return err
}
err = sBox.ParseSetting(name, content, setting.Type, setting.Constraint)
if err != nil {
return err
}
// TODO: Do a reload instead?
SettingBox.Store(sBox)
return nil
}

View File

@ -99,7 +99,7 @@ func ProcessConfig() error {
} }
func VerifyConfig() error { func VerifyConfig() error {
if !Fstore.Exists(Config.DefaultForum) { if !Forums.Exists(Config.DefaultForum) {
return errors.New("Invalid default forum") return errors.New("Invalid default forum")
} }
return nil return nil

View File

@ -67,7 +67,7 @@ func HandleServerSync() error {
if lastUpdate.After(lastSync) { if lastUpdate.After(lastSync) {
// TODO: A more granular sync // TODO: A more granular sync
err = Fstore.LoadForums() err = Forums.LoadForums()
if err != nil { if err != nil {
log.Print("Unable to reload the forums") log.Print("Unable to reload the forums")
return err return err

View File

@ -96,7 +96,7 @@ func compileTemplates() error {
Site: Site, Site: Site,
Settings: SettingBox.Load().(SettingMap), Settings: SettingBox.Load().(SettingMap),
Themes: Themes, Themes: Themes,
ThemeName: DefaultThemeBox.Load().(string), Theme: Themes[DefaultThemeBox.Load().(string)],
NoticeList: []string{"test"}, NoticeList: []string{"test"},
Stylesheets: []string{"panel"}, Stylesheets: []string{"panel"},
Scripts: []string{"whatever"}, Scripts: []string{"whatever"},
@ -132,7 +132,7 @@ func compileTemplates() error {
// TODO: Use a dummy forum list to avoid o(n) problems // TODO: Use a dummy forum list to avoid o(n) problems
var forumList []Forum var forumList []Forum
forums, err := Fstore.GetAll() forums, err := Forums.GetAll()
if err != nil { if err != nil {
return err return err
} }

View File

@ -37,6 +37,8 @@ type CTemplateSet struct {
FragOut string FragOut string
varList map[string]VarItem varList map[string]VarItem
localVars map[string]map[string]VarItemReflect localVars map[string]map[string]VarItemReflect
hasDispInt bool
localDispStructIndex int
stats map[string]int stats map[string]int
pVarList string pVarList string
pVarPosition int pVarPosition int
@ -102,6 +104,8 @@ func (c *CTemplateSet) Compile(name string, dir string, expects string, expectsI
} }
c.varList = varList c.varList = varList
c.hasDispInt = false
c.localDispStructIndex = 0
//c.pVarList = "" //c.pVarList = ""
//c.pVarPosition = 0 //c.pVarPosition = 0
c.stats = make(map[string]int) c.stats = make(map[string]int)
@ -187,7 +191,6 @@ w.Write([]byte(`, " + ", -1)
c.log("Output!") c.log("Output!")
c.log(fout) c.log(fout)
//log.Fatal("remove the log.Fatal line")
return fout, nil return fout, nil
} }
@ -334,10 +337,8 @@ func (c *CTemplateSet) compileSubswitch(varholder string, holdreflect reflect.Va
varbit += ".(" + cur.Type().Name() + ")" varbit += ".(" + cur.Type().Name() + ")"
} }
for _, id := range n.Ident { // ! Might not work so well for non-struct pointers
c.log("Data Kind:", cur.Kind().String()) skipPointers := func(cur reflect.Value, id string) reflect.Value {
c.log("Field Bit:", id)
if cur.Kind() == reflect.Ptr { if cur.Kind() == reflect.Ptr {
c.log("Looping over pointer") c.log("Looping over pointer")
for cur.Kind() == reflect.Ptr { for cur.Kind() == reflect.Ptr {
@ -346,40 +347,87 @@ func (c *CTemplateSet) compileSubswitch(varholder string, holdreflect reflect.Va
c.log("Data Kind:", cur.Kind().String()) c.log("Data Kind:", cur.Kind().String())
c.log("Field Bit:", id) c.log("Field Bit:", id)
} }
return cur
}
var assLines string
var multiline = false
for _, id := range n.Ident {
c.log("Data Kind:", cur.Kind().String())
c.log("Field Bit:", id)
cur = skipPointers(cur, id)
if !cur.IsValid() { if !cur.IsValid() {
if c.debug { c.error("Debug Data:")
fmt.Println("Debug Data:") c.error("Holdreflect:", holdreflect)
fmt.Println("Holdreflect:", holdreflect) c.error("Holdreflect.Kind():", holdreflect.Kind())
fmt.Println("Holdreflect.Kind():", holdreflect.Kind())
if !c.superDebug { if !c.superDebug {
fmt.Println("cur.Kind():", cur.Kind().String()) c.error("cur.Kind():", cur.Kind().String())
} }
fmt.Println("") c.error("")
} if !multiline {
panic(varholder + varbit + "^\n" + "Invalid value. Maybe, it doesn't exist?") panic(varholder + varbit + "^\n" + "Invalid value. Maybe, it doesn't exist?")
} else {
panic(varbit + "^\n" + "Invalid value. Maybe, it doesn't exist?")
}
} }
cur = cur.FieldByName(id) c.log("in-loop varbit: " + varbit)
if cur.Kind() == reflect.Map {
cur = cur.MapIndex(reflect.ValueOf(id))
varbit += "[\"" + id + "\"]"
cur = skipPointers(cur, id)
if cur.Kind() == reflect.Struct || cur.Kind() == reflect.Interface {
// TODO: Move the newVarByte declaration to the top level or to the if level, if a dispInt is only used in a particular if statement
var dispStr, newVarByte string
if cur.Kind() == reflect.Interface { if cur.Kind() == reflect.Interface {
cur = cur.Elem() dispStr = "Int"
// TODO: Surely, there's a better way of detecting this? if !c.hasDispInt {
/*if cur.Kind() == reflect.String && cur.Type().Name() != "string" { newVarByte = ":"
varbit = "string(" + varbit + "." + id + ")"*/ c.hasDispInt = true
//if cur.Kind() == reflect.String && cur.Type().Name() != "string" {
if cur.Type().PkgPath() != "main" && cur.Type().PkgPath() != "" {
c.importMap["html/template"] = "html/template"
varbit += "." + id + ".(" + strings.TrimPrefix(cur.Type().PkgPath(), "html/") + "." + cur.Type().Name() + ")"
} else {
varbit += "." + id + ".(" + cur.Type().Name() + ")"
} }
}
// TODO: De-dupe identical struct types rather than allocating a variable for each one
if cur.Kind() == reflect.Struct {
dispStr = "Struct" + strconv.Itoa(c.localDispStructIndex)
newVarByte = ":"
c.localDispStructIndex++
}
varbit = "disp" + dispStr + " " + newVarByte + "= " + varholder + varbit + "\n"
varholder = "disp" + dispStr
multiline = true
} else { } else {
continue
}
}
if cur.Kind() != reflect.Interface {
cur = cur.FieldByName(id)
varbit += "." + id varbit += "." + id
} }
c.log("End Cycle")
// TODO: Handle deeply nested pointers mixed with interfaces mixed with pointers better
if cur.Kind() == reflect.Interface {
cur = cur.Elem()
varbit += ".("
// TODO: Surely, there's a better way of doing this?
if cur.Type().PkgPath() != "main" && cur.Type().PkgPath() != "" {
c.importMap["html/template"] = "html/template"
varbit += strings.TrimPrefix(cur.Type().PkgPath(), "html/") + "."
} }
out = c.compileVarsub(varholder+varbit, cur) varbit += cur.Type().Name() + ")"
}
c.log("End Cycle: ", varbit)
}
if multiline {
assSplit := strings.Split(varbit, "\n")
varbit = assSplit[len(assSplit)-1]
assSplit = assSplit[:len(assSplit)-1]
assLines = strings.Join(assSplit, "\n") + "\n"
}
varbit = varholder + varbit
out = c.compileVarsub(varbit, cur, assLines)
for _, varItem := range c.varList { for _, varItem := range c.varList {
if strings.HasPrefix(out, varItem.Destination) { if strings.HasPrefix(out, varItem.Destination) {
@ -389,20 +437,21 @@ func (c *CTemplateSet) compileSubswitch(varholder string, holdreflect reflect.Va
return out return out
case *parse.DotNode: case *parse.DotNode:
c.log("Dot Node:", node.String()) c.log("Dot Node:", node.String())
return c.compileVarsub(varholder, holdreflect) return c.compileVarsub(varholder, holdreflect, "")
case *parse.NilNode: case *parse.NilNode:
panic("Nil is not a command x.x") panic("Nil is not a command x.x")
case *parse.VariableNode: case *parse.VariableNode:
c.log("Variable Node:", n.String()) c.log("Variable Node:", n.String())
c.log(n.Ident) c.log(n.Ident)
varname, reflectVal := c.compileIfVarsub(n.String(), varholder, templateName, holdreflect) varname, reflectVal := c.compileIfVarsub(n.String(), varholder, templateName, holdreflect)
return c.compileVarsub(varname, reflectVal) return c.compileVarsub(varname, reflectVal, "")
case *parse.StringNode: case *parse.StringNode:
return n.Quoted return n.Quoted
case *parse.IdentifierNode: case *parse.IdentifierNode:
c.log("Identifier Node:", node) c.log("Identifier Node:", node)
c.log("Identifier Node Args:", node.Args) c.log("Identifier Node Args:", node.Args)
return c.compileVarsub(c.compileIdentSwitch(varholder, holdreflect, templateName, node)) out, outval := c.compileIdentSwitch(varholder, holdreflect, templateName, node)
return c.compileVarsub(out, outval, "")
default: default:
return c.unknownNode(node) return c.unknownNode(node)
} }
@ -525,7 +574,6 @@ func (c *CTemplateSet) compareJoin(varholder string, holdreflect reflect.Value,
func (c *CTemplateSet) compileIdentSwitch(varholder string, holdreflect reflect.Value, templateName string, node *parse.CommandNode) (out string, val reflect.Value) { func (c *CTemplateSet) compileIdentSwitch(varholder string, holdreflect reflect.Value, templateName string, node *parse.CommandNode) (out string, val reflect.Value) {
c.log("in compileIdentSwitch") c.log("in compileIdentSwitch")
//var outbuf map[int]string
ArgLoop: ArgLoop:
for pos := 0; pos < len(node.Args); pos++ { for pos := 0; pos < len(node.Args); pos++ {
id := node.Args[pos] id := node.Args[pos]
@ -728,7 +776,7 @@ func (c *CTemplateSet) compileBoolsub(varname string, varholder string, template
return out return out
} }
func (c *CTemplateSet) compileVarsub(varname string, val reflect.Value) string { func (c *CTemplateSet) compileVarsub(varname string, val reflect.Value, assLines string) (out string) {
c.log("in compileVarsub") c.log("in compileVarsub")
for _, varItem := range c.varList { for _, varItem := range c.varList {
if strings.HasPrefix(varname, varItem.Destination) { if strings.HasPrefix(varname, varItem.Destination) {
@ -747,29 +795,33 @@ func (c *CTemplateSet) compileVarsub(varname string, val reflect.Value) string {
val = val.Elem() val = val.Elem()
} }
c.log("varname: ", varname)
c.log("assLines: ", assLines)
switch val.Kind() { switch val.Kind() {
case reflect.Int: case reflect.Int:
c.importMap["strconv"] = "strconv" c.importMap["strconv"] = "strconv"
return "w.Write([]byte(strconv.Itoa(" + varname + ")))\n" out = "w.Write([]byte(strconv.Itoa(" + varname + ")))\n"
case reflect.Bool: case reflect.Bool:
return "if " + varname + " {\nw.Write([]byte(\"true\"))} else {\nw.Write([]byte(\"false\"))\n}\n" out = "if " + varname + " {\nw.Write([]byte(\"true\"))} else {\nw.Write([]byte(\"false\"))\n}\n"
case reflect.String: case reflect.String:
if val.Type().Name() != "string" && !strings.HasPrefix(varname, "string(") { if val.Type().Name() != "string" && !strings.HasPrefix(varname, "string(") {
return "w.Write([]byte(string(" + varname + ")))\n" varname = "string(" + varname + ")"
} }
return "w.Write([]byte(" + varname + "))\n" out = "w.Write([]byte(" + varname + "))\n"
case reflect.Int64: case reflect.Int64:
c.importMap["strconv"] = "strconv" c.importMap["strconv"] = "strconv"
return "w.Write([]byte(strconv.FormatInt(" + varname + ", 10)))" out = "w.Write([]byte(strconv.FormatInt(" + varname + ", 10)))"
default: default:
if !val.IsValid() { if !val.IsValid() {
panic(varname + "^\n" + "Invalid value. Maybe, it doesn't exist?") panic(assLines + varname + "^\n" + "Invalid value. Maybe, it doesn't exist?")
} }
fmt.Println("Unknown Variable Name:", varname) fmt.Println("Unknown Variable Name:", varname)
fmt.Println("Unknown Kind:", val.Kind()) fmt.Println("Unknown Kind:", val.Kind())
fmt.Println("Unknown Type:", val.Type().Name()) fmt.Println("Unknown Type:", val.Type().Name())
panic("// I don't know what this variable's type is o.o\n") panic("// I don't know what this variable's type is o.o\n")
} }
c.log("out: ", out)
return assLines + out
} }
func (c *CTemplateSet) compileSubtemplate(pvarholder string, pholdreflect reflect.Value, node *parse.TemplateNode) (out string) { func (c *CTemplateSet) compileSubtemplate(pvarholder string, pholdreflect reflect.Value, node *parse.TemplateNode) (out string) {
@ -831,6 +883,12 @@ func (c *CTemplateSet) log(args ...interface{}) {
} }
} }
func (c *CTemplateSet) error(args ...interface{}) {
if c.debug {
fmt.Println(args...)
}
}
func (c *CTemplateSet) compileCommand(*parse.CommandNode) (out string) { func (c *CTemplateSet) compileCommand(*parse.CommandNode) (out string) {
panic("Uh oh! Something went wrong!") panic("Uh oh! Something went wrong!")
} }

View File

@ -22,7 +22,7 @@ import (
"../query_gen/lib" "../query_gen/lib"
) )
type ThemeList map[string]Theme type ThemeList map[string]Theme // ? Use pointers instead?
var Themes ThemeList = make(map[string]Theme) var Themes ThemeList = make(map[string]Theme)
var DefaultThemeBox atomic.Value var DefaultThemeBox atomic.Value
@ -47,6 +47,7 @@ type Theme struct {
Tag string Tag string
URL string URL string
Sidebars string // Allowed Values: left, right, both, false Sidebars string // Allowed Values: left, right, both, false
AboutSegment bool // ? - Should this be a theme var instead?
//DisableMinifier // Is this really a good idea? I don't think themes should be fighting against the minifier //DisableMinifier // Is this really a good idea? I don't think themes should be fighting against the minifier
Settings map[string]ThemeSetting Settings map[string]ThemeSetting
Templates []TemplateMapping Templates []TemplateMapping

View File

@ -149,9 +149,9 @@ func init() {
// Flush the topic out of the cache // Flush the topic out of the cache
// ? - We do a CacheRemove() here instead of mutating the pointer to avoid creating a race condition // ? - We do a CacheRemove() here instead of mutating the pointer to avoid creating a race condition
func (topic *Topic) cacheRemove() { func (topic *Topic) cacheRemove() {
tcache, ok := Topics.(TopicCache) tcache := Topics.(TopicCache)
if ok { if tcache != nil {
tcache.CacheRemove(topic.ID) tcache.Remove(topic.ID)
} }
} }
@ -226,7 +226,7 @@ func (topic *Topic) Delete() error {
return err return err
} }
err = Fstore.RemoveTopic(topic.ParentID) err = Forums.RemoveTopic(topic.ParentID)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
return err return err
} }
@ -263,10 +263,10 @@ func (topic *Topic) Copy() Topic {
// TODO: Refactor the caller to take a Topic and a User rather than a combined TopicUser // TODO: Refactor the caller to take a Topic and a User rather than a combined TopicUser
func GetTopicUser(tid int) (TopicUser, error) { func GetTopicUser(tid int) (TopicUser, error) {
tcache, tok := Topics.(TopicCache) tcache := Topics.GetCache()
ucache, uok := Users.(UserCache) ucache := Users.GetCache()
if tok && uok { if tcache != nil && ucache != nil {
topic, err := tcache.CacheGet(tid) topic, err := tcache.Get(tid)
if err == nil { if err == nil {
user, err := Users.Get(topic.CreatedBy) user, err := Users.Get(topic.CreatedBy)
if err != nil { if err != nil {
@ -292,12 +292,12 @@ func GetTopicUser(tid int) (TopicUser, error) {
err := topicStmts.getTopicUser.QueryRow(tid).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IPAddress, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level) err := topicStmts.getTopicUser.QueryRow(tid).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IPAddress, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
tu.Link = BuildTopicURL(NameToSlug(tu.Title), tu.ID) tu.Link = BuildTopicURL(NameToSlug(tu.Title), tu.ID)
tu.UserLink = BuildProfileURL(NameToSlug(tu.CreatedByName), tu.CreatedBy) tu.UserLink = BuildProfileURL(NameToSlug(tu.CreatedByName), tu.CreatedBy)
tu.Tag = Gstore.DirtyGet(tu.Group).Tag tu.Tag = Groups.DirtyGet(tu.Group).Tag
if tok { if tcache != nil {
theTopic := Topic{ID: tu.ID, Link: tu.Link, Title: tu.Title, Content: tu.Content, CreatedBy: tu.CreatedBy, IsClosed: tu.IsClosed, Sticky: tu.Sticky, CreatedAt: tu.CreatedAt, LastReplyAt: tu.LastReplyAt, ParentID: tu.ParentID, IPAddress: tu.IPAddress, PostCount: tu.PostCount, LikeCount: tu.LikeCount} theTopic := Topic{ID: tu.ID, Link: tu.Link, Title: tu.Title, Content: tu.Content, CreatedBy: tu.CreatedBy, IsClosed: tu.IsClosed, Sticky: tu.Sticky, CreatedAt: tu.CreatedAt, LastReplyAt: tu.LastReplyAt, ParentID: tu.ParentID, IPAddress: tu.IPAddress, PostCount: tu.PostCount, LikeCount: tu.LikeCount}
//log.Printf("theTopic: %+v\n", theTopic) //log.Printf("theTopic: %+v\n", theTopic)
_ = tcache.CacheAdd(&theTopic) _ = tcache.Add(&theTopic)
} }
return tu, err return tu, err
} }

127
common/topic_cache.go Normal file
View File

@ -0,0 +1,127 @@
package common
import (
"sync"
"sync/atomic"
)
type TopicCache interface {
Get(id int) (*Topic, error)
GetUnsafe(id int) (*Topic, error)
Set(item *Topic) error
Add(item *Topic) error
AddUnsafe(item *Topic) error
Remove(id int) error
RemoveUnsafe(id int) error
Flush()
Length() int
SetCapacity(capacity int)
GetCapacity() int
}
type MemoryTopicCache struct {
items map[int]*Topic
length int64 // sync/atomic only lets us operate on int32s and int64s
capacity int
sync.RWMutex
}
// NewMemoryTopicCache gives you a new instance of MemoryTopicCache
func NewMemoryTopicCache(capacity int) *MemoryTopicCache {
return &MemoryTopicCache{
items: make(map[int]*Topic),
capacity: capacity,
}
}
func (mts *MemoryTopicCache) Get(id int) (*Topic, error) {
mts.RLock()
item, ok := mts.items[id]
mts.RUnlock()
if ok {
return item, nil
}
return item, ErrNoRows
}
func (mts *MemoryTopicCache) GetUnsafe(id int) (*Topic, error) {
item, ok := mts.items[id]
if ok {
return item, nil
}
return item, ErrNoRows
}
func (mts *MemoryTopicCache) Set(item *Topic) error {
mts.Lock()
_, ok := mts.items[item.ID]
if ok {
mts.items[item.ID] = item
} else if int(mts.length) >= mts.capacity {
mts.Unlock()
return ErrStoreCapacityOverflow
} else {
mts.items[item.ID] = item
atomic.AddInt64(&mts.length, 1)
}
mts.Unlock()
return nil
}
func (mts *MemoryTopicCache) Add(item *Topic) error {
if int(mts.length) >= mts.capacity {
return ErrStoreCapacityOverflow
}
mts.Lock()
mts.items[item.ID] = item
mts.Unlock()
atomic.AddInt64(&mts.length, 1)
return nil
}
// TODO: Make these length increments thread-safe. Ditto for the other DataStores
func (mts *MemoryTopicCache) AddUnsafe(item *Topic) error {
if int(mts.length) >= mts.capacity {
return ErrStoreCapacityOverflow
}
mts.items[item.ID] = item
atomic.AddInt64(&mts.length, 1)
return nil
}
// TODO: Make these length decrements thread-safe. Ditto for the other DataStores
func (mts *MemoryTopicCache) Remove(id int) error {
mts.Lock()
delete(mts.items, id)
mts.Unlock()
atomic.AddInt64(&mts.length, -1)
return nil
}
func (mts *MemoryTopicCache) RemoveUnsafe(id int) error {
delete(mts.items, id)
atomic.AddInt64(&mts.length, -1)
return nil
}
func (mts *MemoryTopicCache) Flush() {
mts.Lock()
mts.items = make(map[int]*Topic)
mts.length = 0
mts.Unlock()
}
// ! Is this concurrent?
// Length returns the number of topics in the memory cache
func (mts *MemoryTopicCache) Length() int {
return int(mts.length)
}
func (mts *MemoryTopicCache) SetCapacity(capacity int) {
mts.capacity = capacity
}
func (mts *MemoryTopicCache) GetCapacity() int {
return mts.capacity
}

View File

@ -10,8 +10,6 @@ import (
"database/sql" "database/sql"
"errors" "errors"
"strings" "strings"
"sync"
"sync/atomic"
"../query_gen/lib" "../query_gen/lib"
) )
@ -31,44 +29,33 @@ type TopicStore interface {
Exists(id int) bool Exists(id int) bool
Create(fid int, topicName string, content string, uid int, ipaddress string) (tid int, err error) Create(fid int, topicName string, content string, uid int, ipaddress string) (tid int, err error)
AddLastTopic(item *Topic, fid int) error // unimplemented AddLastTopic(item *Topic, fid int) error // unimplemented
Reload(id int) error // Too much SQL logic to move into TopicCache
// TODO: Implement these two methods // TODO: Implement these two methods
//Replies(tid int) ([]*Reply, error) //Replies(tid int) ([]*Reply, error)
//RepliesRange(tid int, lower int, higher int) ([]*Reply, error) //RepliesRange(tid int, lower int, higher int) ([]*Reply, error)
GlobalCount() int GlobalCount() int
SetCache(cache TopicCache)
GetCache() TopicCache
} }
type TopicCache interface { type DefaultTopicStore struct {
CacheGet(id int) (*Topic, error) cache TopicCache
CacheGetUnsafe(id int) (*Topic, error)
CacheSet(item *Topic) error
CacheAdd(item *Topic) error
CacheAddUnsafe(item *Topic) error
CacheRemove(id int) error
CacheRemoveUnsafe(id int) error
Flush()
Reload(id int) error
Length() int
SetCapacity(capacity int)
GetCapacity() int
}
type MemoryTopicStore struct {
items map[int]*Topic
length int64 // sync/atomic only lets us operate on int32s and int64s
capacity int
get *sql.Stmt get *sql.Stmt
exists *sql.Stmt exists *sql.Stmt
topicCount *sql.Stmt topicCount *sql.Stmt
create *sql.Stmt create *sql.Stmt
sync.RWMutex
} }
// NewMemoryTopicStore gives you a new instance of MemoryTopicStore // NewDefaultTopicStore gives you a new instance of DefaultTopicStore
func NewMemoryTopicStore(capacity int) (*MemoryTopicStore, error) { func NewDefaultTopicStore(cache TopicCache) (*DefaultTopicStore, error) {
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
return &MemoryTopicStore{ if cache == nil {
items: make(map[int]*Topic), cache = NewNullTopicCache()
capacity: capacity, }
return &DefaultTopicStore{
cache: cache,
get: acc.Select("topics").Columns("title, content, createdBy, createdAt, lastReplyAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data").Where("tid = ?").Prepare(), get: acc.Select("topics").Columns("title, content, createdBy, createdAt, lastReplyAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data").Where("tid = ?").Prepare(),
exists: acc.Select("topics").Columns("tid").Where("tid = ?").Prepare(), exists: acc.Select("topics").Columns("tid").Where("tid = ?").Prepare(),
topicCount: acc.Count("topics").Prepare(), topicCount: acc.Count("topics").Prepare(),
@ -76,84 +63,63 @@ func NewMemoryTopicStore(capacity int) (*MemoryTopicStore, error) {
}, acc.FirstError() }, acc.FirstError()
} }
func (mts *MemoryTopicStore) CacheGet(id int) (*Topic, error) { func (mts *DefaultTopicStore) DirtyGet(id int) *Topic {
mts.RLock() topic, err := mts.cache.Get(id)
item, ok := mts.items[id] if err == nil {
mts.RUnlock()
if ok {
return item, nil
}
return item, ErrNoRows
}
func (mts *MemoryTopicStore) CacheGetUnsafe(id int) (*Topic, error) {
item, ok := mts.items[id]
if ok {
return item, nil
}
return item, ErrNoRows
}
func (mts *MemoryTopicStore) DirtyGet(id int) *Topic {
mts.RLock()
topic, ok := mts.items[id]
mts.RUnlock()
if ok {
return topic return topic
} }
topic = &Topic{ID: id} topic = &Topic{ID: id}
err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err = mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
if err == nil { if err == nil {
topic.Link = BuildTopicURL(NameToSlug(topic.Title), id) topic.Link = BuildTopicURL(NameToSlug(topic.Title), id)
_ = mts.CacheAdd(topic) _ = mts.cache.Add(topic)
return topic return topic
} }
return BlankTopic() return BlankTopic()
} }
func (mts *MemoryTopicStore) Get(id int) (*Topic, error) { // TODO: Log weird cache errors?
mts.RLock() func (mts *DefaultTopicStore) Get(id int) (topic *Topic, err error) {
topic, ok := mts.items[id] topic, err = mts.cache.Get(id)
mts.RUnlock() if err == nil {
if ok {
return topic, nil return topic, nil
} }
topic = &Topic{ID: id} topic = &Topic{ID: id}
err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err = mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
if err == nil { if err == nil {
topic.Link = BuildTopicURL(NameToSlug(topic.Title), id) topic.Link = BuildTopicURL(NameToSlug(topic.Title), id)
_ = mts.CacheAdd(topic) _ = mts.cache.Add(topic)
} }
return topic, err return topic, err
} }
// BypassGet will always bypass the cache and pull the topic directly from the database // BypassGet will always bypass the cache and pull the topic directly from the database
func (mts *MemoryTopicStore) BypassGet(id int) (*Topic, error) { func (mts *DefaultTopicStore) BypassGet(id int) (*Topic, error) {
topic := &Topic{ID: id} topic := &Topic{ID: id}
err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
topic.Link = BuildTopicURL(NameToSlug(topic.Title), id) topic.Link = BuildTopicURL(NameToSlug(topic.Title), id)
return topic, err return topic, err
} }
func (mts *MemoryTopicStore) Reload(id int) error { func (mts *DefaultTopicStore) Reload(id int) error {
topic := &Topic{ID: id} topic := &Topic{ID: id}
err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
if err == nil { if err == nil {
topic.Link = BuildTopicURL(NameToSlug(topic.Title), id) topic.Link = BuildTopicURL(NameToSlug(topic.Title), id)
_ = mts.CacheSet(topic) _ = mts.cache.Set(topic)
} else { } else {
_ = mts.CacheRemove(id) _ = mts.cache.Remove(id)
} }
return err return err
} }
func (mts *MemoryTopicStore) Exists(id int) bool { func (mts *DefaultTopicStore) Exists(id int) bool {
return mts.exists.QueryRow(id).Scan(&id) == nil return mts.exists.QueryRow(id).Scan(&id) == nil
} }
func (mts *MemoryTopicStore) Create(fid int, topicName string, content string, uid int, ipaddress string) (tid int, err error) { func (mts *DefaultTopicStore) Create(fid int, topicName string, content string, uid int, ipaddress string) (tid int, err error) {
topicName = strings.TrimSpace(topicName) topicName = strings.TrimSpace(topicName)
if topicName == "" { if topicName == "" {
return 0, ErrNoBody return 0, ErrNoBody
@ -177,91 +143,17 @@ func (mts *MemoryTopicStore) Create(fid int, topicName string, content string, u
return 0, err return 0, err
} }
return int(lastID), Fstore.AddTopic(int(lastID), uid, fid) return int(lastID), Forums.AddTopic(int(lastID), uid, fid)
}
func (mts *MemoryTopicStore) CacheSet(item *Topic) error {
mts.Lock()
_, ok := mts.items[item.ID]
if ok {
mts.items[item.ID] = item
} else if int(mts.length) >= mts.capacity {
mts.Unlock()
return ErrStoreCapacityOverflow
} else {
mts.items[item.ID] = item
atomic.AddInt64(&mts.length, 1)
}
mts.Unlock()
return nil
}
func (mts *MemoryTopicStore) CacheAdd(item *Topic) error {
if int(mts.length) >= mts.capacity {
return ErrStoreCapacityOverflow
}
mts.Lock()
mts.items[item.ID] = item
mts.Unlock()
atomic.AddInt64(&mts.length, 1)
return nil
}
// TODO: Make these length increments thread-safe. Ditto for the other DataStores
func (mts *MemoryTopicStore) CacheAddUnsafe(item *Topic) error {
if int(mts.length) >= mts.capacity {
return ErrStoreCapacityOverflow
}
mts.items[item.ID] = item
atomic.AddInt64(&mts.length, 1)
return nil
}
// TODO: Make these length decrements thread-safe. Ditto for the other DataStores
func (mts *MemoryTopicStore) CacheRemove(id int) error {
mts.Lock()
delete(mts.items, id)
mts.Unlock()
atomic.AddInt64(&mts.length, -1)
return nil
}
func (mts *MemoryTopicStore) CacheRemoveUnsafe(id int) error {
delete(mts.items, id)
atomic.AddInt64(&mts.length, -1)
return nil
} }
// ? - What is this? Do we need it? Should it be in the main store interface? // ? - What is this? Do we need it? Should it be in the main store interface?
func (mts *MemoryTopicStore) AddLastTopic(item *Topic, fid int) error { func (mts *DefaultTopicStore) AddLastTopic(item *Topic, fid int) error {
// Coming Soon... // Coming Soon...
return nil return nil
} }
func (mts *MemoryTopicStore) Flush() {
mts.Lock()
mts.items = make(map[int]*Topic)
mts.length = 0
mts.Unlock()
}
// ! Is this concurrent?
// Length returns the number of topics in the memory cache
func (mts *MemoryTopicStore) Length() int {
return int(mts.length)
}
func (mts *MemoryTopicStore) SetCapacity(capacity int) {
mts.capacity = capacity
}
func (mts *MemoryTopicStore) GetCapacity() int {
return mts.capacity
}
// GlobalCount returns the total number of topics on these forums // GlobalCount returns the total number of topics on these forums
func (mts *MemoryTopicStore) GlobalCount() int { func (mts *DefaultTopicStore) GlobalCount() (tcount int) {
var tcount int
err := mts.topicCount.QueryRow().Scan(&tcount) err := mts.topicCount.QueryRow().Scan(&tcount)
if err != nil { if err != nil {
LogError(err) LogError(err)
@ -269,91 +161,15 @@ func (mts *MemoryTopicStore) GlobalCount() int {
return tcount return tcount
} }
type SQLTopicStore struct { func (mts *DefaultTopicStore) SetCache(cache TopicCache) {
get *sql.Stmt mts.cache = cache
exists *sql.Stmt
topicCount *sql.Stmt
create *sql.Stmt
} }
func NewSQLTopicStore() (*SQLTopicStore, error) { // TODO: We're temporarily doing this so that you can do tcache != nil in getTopicUser. Refactor it.
acc := qgen.Builder.Accumulator() func (mts *DefaultTopicStore) GetCache() TopicCache {
return &SQLTopicStore{ _, ok := mts.cache.(*NullTopicCache)
get: acc.Select("topics").Columns("title, content, createdBy, createdAt, lastReplyAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data").Where("tid = ?").Prepare(), if ok {
exists: acc.Select("topics").Columns("tid").Where("tid = ?").Prepare(),
topicCount: 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()
}
func (sts *SQLTopicStore) DirtyGet(id int) *Topic {
topic := &Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
topic.Link = BuildTopicURL(NameToSlug(topic.Title), id)
if err != nil {
return BlankTopic()
}
return topic
}
func (sts *SQLTopicStore) Get(id int) (*Topic, error) {
topic := Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
topic.Link = BuildTopicURL(NameToSlug(topic.Title), id)
return &topic, err
}
// BypassGet is an alias of Get(), as we don't have a cache for SQLTopicStore
func (sts *SQLTopicStore) BypassGet(id int) (*Topic, error) {
topic := &Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
topic.Link = BuildTopicURL(NameToSlug(topic.Title), id)
return topic, err
}
func (sts *SQLTopicStore) Exists(id int) bool {
return sts.exists.QueryRow(id).Scan(&id) == nil
}
func (sts *SQLTopicStore) Create(fid int, topicName string, content string, uid int, ipaddress string) (tid int, err error) {
topicName = strings.TrimSpace(topicName)
if topicName == "" {
return 0, ErrNoBody
}
content = strings.TrimSpace(content)
parsedContent := ParseMessage(content, fid, "forums")
if strings.TrimSpace(parsedContent) == "" {
return 0, ErrNoBody
}
wcount := WordCount(content)
// TODO: Move this statement into the topic store
res, err := sts.create.Exec(fid, topicName, content, parsedContent, uid, ipaddress, wcount, uid)
if err != nil {
return 0, err
}
lastID, err := res.LastInsertId()
if err != nil {
return 0, err
}
return int(lastID), Fstore.AddTopic(int(lastID), uid, fid)
}
// ? - What're we going to do about this?
func (sts *SQLTopicStore) AddLastTopic(item *Topic, fid int) error {
// Coming Soon...
return nil return nil
} }
return mts.cache
// GlobalCount returns the total number of topics on these forums
func (sts *SQLTopicStore) GlobalCount() int {
var tcount int
err := sts.topicCount.QueryRow().Scan(&tcount)
if err != nil {
LogError(err)
}
return tcount
} }

View File

@ -7,8 +7,6 @@
package common package common
import ( import (
//"log"
//"fmt"
"database/sql" "database/sql"
"errors" "errors"
"strconv" "strconv"
@ -111,14 +109,15 @@ func (user *User) Init() {
user.Avatar = strings.Replace(Config.Noavatar, "{id}", strconv.Itoa(user.ID), 1) user.Avatar = strings.Replace(Config.Noavatar, "{id}", strconv.Itoa(user.ID), 1)
} }
user.Link = BuildProfileURL(NameToSlug(user.Name), user.ID) user.Link = BuildProfileURL(NameToSlug(user.Name), user.ID)
user.Tag = Gstore.DirtyGet(user.Group).Tag user.Tag = Groups.DirtyGet(user.Group).Tag
user.InitPerms() user.InitPerms()
} }
// TODO: Refactor this idiom into something shorter, maybe with a NullUserCache when one isn't set?
func (user *User) CacheRemove() { func (user *User) CacheRemove() {
ucache, ok := Users.(UserCache) ucache := Users.GetCache()
if ok { if ucache != nil {
ucache.CacheRemove(user.ID) ucache.Remove(user.ID)
} }
} }
@ -337,7 +336,7 @@ func (user *User) InitPerms() {
user.Group = user.TempGroup user.Group = user.TempGroup
} }
group := Gstore.DirtyGet(user.Group) group := Groups.DirtyGet(user.Group)
if user.IsSuperAdmin { if user.IsSuperAdmin {
user.Perms = AllPerms user.Perms = AllPerms
user.PluginPerms = AllPluginPerms user.PluginPerms = AllPluginPerms

146
common/user_cache.go Normal file
View File

@ -0,0 +1,146 @@
package common
import (
"sync"
"sync/atomic"
)
type UserCache interface {
Get(id int) (*User, error)
GetUnsafe(id int) (*User, error)
BulkGet(ids []int) (list []*User)
Set(item *User) error
Add(item *User) error
AddUnsafe(item *User) error
Remove(id int) error
RemoveUnsafe(id int) error
Flush()
Length() int
SetCapacity(capacity int)
GetCapacity() int
}
type MemoryUserCache struct {
items map[int]*User
length int64
capacity int
sync.RWMutex
}
// NewMemoryUserCache gives you a new instance of MemoryUserCache
func NewMemoryUserCache(capacity int) *MemoryUserCache {
return &MemoryUserCache{
items: make(map[int]*User),
capacity: capacity,
}
}
func (mus *MemoryUserCache) Get(id int) (*User, error) {
mus.RLock()
item, ok := mus.items[id]
mus.RUnlock()
if ok {
return item, nil
}
return item, ErrNoRows
}
func (mus *MemoryUserCache) BulkGet(ids []int) (list []*User) {
list = make([]*User, len(ids))
mus.RLock()
for i, id := range ids {
list[i] = mus.items[id]
}
mus.RUnlock()
return list
}
func (mus *MemoryUserCache) GetUnsafe(id int) (*User, error) {
item, ok := mus.items[id]
if ok {
return item, nil
}
return item, ErrNoRows
}
func (mus *MemoryUserCache) Set(item *User) error {
mus.Lock()
user, ok := mus.items[item.ID]
if ok {
mus.Unlock()
*user = *item
} else if int(mus.length) >= mus.capacity {
mus.Unlock()
return ErrStoreCapacityOverflow
} else {
mus.items[item.ID] = item
mus.Unlock()
atomic.AddInt64(&mus.length, 1)
}
return nil
}
func (mus *MemoryUserCache) Add(item *User) error {
if int(mus.length) >= mus.capacity {
return ErrStoreCapacityOverflow
}
mus.Lock()
mus.items[item.ID] = item
mus.length = int64(len(mus.items))
mus.Unlock()
return nil
}
func (mus *MemoryUserCache) AddUnsafe(item *User) error {
if int(mus.length) >= mus.capacity {
return ErrStoreCapacityOverflow
}
mus.items[item.ID] = item
mus.length = int64(len(mus.items))
return nil
}
func (mus *MemoryUserCache) Remove(id int) error {
mus.Lock()
_, ok := mus.items[id]
if !ok {
mus.Unlock()
return ErrNoRows
}
delete(mus.items, id)
mus.Unlock()
atomic.AddInt64(&mus.length, -1)
return nil
}
func (mus *MemoryUserCache) RemoveUnsafe(id int) error {
_, ok := mus.items[id]
if !ok {
return ErrNoRows
}
delete(mus.items, id)
atomic.AddInt64(&mus.length, -1)
return nil
}
func (mus *MemoryUserCache) Flush() {
mus.Lock()
mus.items = make(map[int]*User)
mus.length = 0
mus.Unlock()
}
// ! Is this concurrent?
// Length returns the number of users in the memory cache
func (mus *MemoryUserCache) Length() int {
return int(mus.length)
}
func (mus *MemoryUserCache) SetCapacity(capacity int) {
mus.capacity = capacity
}
func (mus *MemoryUserCache) GetCapacity() int {
return mus.capacity
}

View File

@ -5,8 +5,6 @@ import (
"errors" "errors"
"log" "log"
"strconv" "strconv"
"sync"
"sync/atomic"
"../query_gen/lib" "../query_gen/lib"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@ -25,43 +23,32 @@ type UserStore interface {
BulkGetMap(ids []int) (map[int]*User, error) BulkGetMap(ids []int) (map[int]*User, error)
BypassGet(id int) (*User, error) BypassGet(id int) (*User, error)
Create(username string, password string, email string, group int, active bool) (int, error) Create(username string, password string, email string, group int, active bool) (int, error)
GlobalCount() int
}
type UserCache interface {
CacheGet(id int) (*User, error)
CacheGetUnsafe(id int) (*User, error)
CacheSet(item *User) error
CacheAdd(item *User) error
CacheAddUnsafe(item *User) error
CacheRemove(id int) error
CacheRemoveUnsafe(id int) error
Flush()
Reload(id int) error Reload(id int) error
Length() int GlobalCount() int
SetCapacity(capacity int)
GetCapacity() int SetCache(cache UserCache)
GetCache() UserCache
} }
type MemoryUserStore struct { type DefaultUserStore struct {
items map[int]*User cache UserCache
length int64
capacity int
get *sql.Stmt get *sql.Stmt
exists *sql.Stmt exists *sql.Stmt
register *sql.Stmt register *sql.Stmt
usernameExists *sql.Stmt usernameExists *sql.Stmt
userCount *sql.Stmt userCount *sql.Stmt
sync.RWMutex
} }
// NewMemoryUserStore gives you a new instance of MemoryUserStore // NewDefaultUserStore gives you a new instance of DefaultUserStore
func NewMemoryUserStore(capacity int) (*MemoryUserStore, error) { func NewDefaultUserStore(cache UserCache) (*DefaultUserStore, error) {
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
if cache == nil {
cache = NewNullUserCache()
}
// TODO: Add an admin version of registerStmt with more flexibility? // TODO: Add an admin version of registerStmt with more flexibility?
return &MemoryUserStore{ return &DefaultUserStore{
items: make(map[int]*User), cache: cache,
capacity: capacity,
get: acc.SimpleSelect("users", "name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid = ?", "", ""), get: acc.SimpleSelect("users", "name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid = ?", "", ""),
exists: acc.SimpleSelect("users", "uid", "uid = ?", "", ""), exists: acc.SimpleSelect("users", "uid", "uid = ?", "", ""),
register: acc.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt", "?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()"), register: acc.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt", "?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()"),
@ -70,75 +57,43 @@ func NewMemoryUserStore(capacity int) (*MemoryUserStore, error) {
}, acc.FirstError() }, acc.FirstError()
} }
func (mus *MemoryUserStore) CacheGet(id int) (*User, error) { func (mus *DefaultUserStore) DirtyGet(id int) *User {
mus.RLock() user, err := mus.cache.Get(id)
item, ok := mus.items[id] if err == nil {
mus.RUnlock()
if ok {
return item, nil
}
return item, ErrNoRows
}
func (mus *MemoryUserStore) CacheGetUnsafe(id int) (*User, error) {
item, ok := mus.items[id]
if ok {
return item, nil
}
return item, ErrNoRows
}
func (mus *MemoryUserStore) DirtyGet(id int) *User {
mus.RLock()
user, ok := mus.items[id]
mus.RUnlock()
if ok {
return user return user
} }
user = &User{ID: id, Loggedin: true} user = &User{ID: id, Loggedin: true}
err := mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup) err = mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
user.Init() user.Init()
if err == nil { if err == nil {
mus.CacheSet(user) mus.cache.Set(user)
return user return user
} }
return BlankUser() return BlankUser()
} }
func (mus *MemoryUserStore) Get(id int) (*User, error) { // TODO: Log weird cache errors? Not just here but in every *Cache?
mus.RLock() func (mus *DefaultUserStore) Get(id int) (*User, error) {
user, ok := mus.items[id] user, err := mus.cache.Get(id)
mus.RUnlock() if err == nil {
if ok {
return user, nil return user, nil
} }
user = &User{ID: id, Loggedin: true} user = &User{ID: id, Loggedin: true}
err := mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup) err = mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
user.Init() user.Init()
if err == nil { if err == nil {
mus.CacheSet(user) mus.cache.Set(user)
} }
return user, err return user, err
} }
// WARNING: We did a little hack to make this as thin and quick as possible to reduce lock contention, use the * Cascade* methods instead for normal use
func (mus *MemoryUserStore) bulkGet(ids []int) (list []*User) {
list = make([]*User, len(ids))
mus.RLock()
for i, id := range ids {
list[i] = mus.items[id]
}
mus.RUnlock()
return list
}
// TODO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts? // TODO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts?
// TODO: ID of 0 should always error? // TODO: ID of 0 should always error?
func (mus *MemoryUserStore) BulkGetMap(ids []int) (list map[int]*User, err error) { func (mus *DefaultUserStore) BulkGetMap(ids []int) (list map[int]*User, err error) {
var idCount = len(ids) var idCount = len(ids)
list = make(map[int]*User) list = make(map[int]*User)
if idCount == 0 { if idCount == 0 {
@ -146,7 +101,7 @@ func (mus *MemoryUserStore) BulkGetMap(ids []int) (list map[int]*User, err error
} }
var stillHere []int var stillHere []int
sliceList := mus.bulkGet(ids) sliceList := mus.cache.BulkGet(ids)
for i, sliceItem := range sliceList { for i, sliceItem := range sliceList {
if sliceItem != nil { if sliceItem != nil {
list[sliceItem.ID] = sliceItem list[sliceItem.ID] = sliceItem
@ -184,7 +139,7 @@ func (mus *MemoryUserStore) BulkGetMap(ids []int) (list map[int]*User, err error
} }
user.Init() user.Init()
mus.CacheSet(user) mus.cache.Set(user)
list[user.ID] = user list[user.ID] = user
} }
@ -216,7 +171,7 @@ func (mus *MemoryUserStore) BulkGetMap(ids []int) (list map[int]*User, err error
return list, err return list, err
} }
func (mus *MemoryUserStore) BypassGet(id int) (*User, error) { func (mus *DefaultUserStore) BypassGet(id int) (*User, error) {
user := &User{ID: id, Loggedin: true} user := &User{ID: id, Loggedin: true}
err := mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup) err := mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
@ -224,20 +179,20 @@ func (mus *MemoryUserStore) BypassGet(id int) (*User, error) {
return user, err return user, err
} }
func (mus *MemoryUserStore) Reload(id int) error { func (mus *DefaultUserStore) Reload(id int) error {
user := &User{ID: id, Loggedin: true} user := &User{ID: id, Loggedin: true}
err := mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup) err := mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
if err != nil { if err != nil {
mus.CacheRemove(id) mus.cache.Remove(id)
return err return err
} }
user.Init() user.Init()
_ = mus.CacheSet(user) _ = mus.cache.Set(user)
return nil return nil
} }
func (mus *MemoryUserStore) Exists(id int) bool { func (mus *DefaultUserStore) Exists(id int) bool {
err := mus.exists.QueryRow(id).Scan(&id) err := mus.exists.QueryRow(id).Scan(&id)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
LogError(err) LogError(err)
@ -245,210 +200,8 @@ func (mus *MemoryUserStore) Exists(id int) bool {
return err != ErrNoRows return err != ErrNoRows
} }
func (mus *MemoryUserStore) CacheSet(item *User) error {
mus.Lock()
user, ok := mus.items[item.ID]
if ok {
mus.Unlock()
*user = *item
} else if int(mus.length) >= mus.capacity {
mus.Unlock()
return ErrStoreCapacityOverflow
} else {
mus.items[item.ID] = item
mus.Unlock()
atomic.AddInt64(&mus.length, 1)
}
return nil
}
func (mus *MemoryUserStore) CacheAdd(item *User) error {
if int(mus.length) >= mus.capacity {
return ErrStoreCapacityOverflow
}
mus.Lock()
mus.items[item.ID] = item
mus.length = int64(len(mus.items))
mus.Unlock()
return nil
}
func (mus *MemoryUserStore) CacheAddUnsafe(item *User) error {
if int(mus.length) >= mus.capacity {
return ErrStoreCapacityOverflow
}
mus.items[item.ID] = item
mus.length = int64(len(mus.items))
return nil
}
func (mus *MemoryUserStore) CacheRemove(id int) error {
mus.Lock()
_, ok := mus.items[id]
if !ok {
mus.Unlock()
return ErrNoRows
}
delete(mus.items, id)
mus.Unlock()
atomic.AddInt64(&mus.length, -1)
return nil
}
func (mus *MemoryUserStore) CacheRemoveUnsafe(id int) error {
_, ok := mus.items[id]
if !ok {
return ErrNoRows
}
delete(mus.items, id)
atomic.AddInt64(&mus.length, -1)
return nil
}
// TODO: Change active to a bool? // TODO: Change active to a bool?
func (mus *MemoryUserStore) Create(username string, password string, email string, group int, active bool) (int, error) { func (mus *DefaultUserStore) Create(username string, password string, email string, group int, active bool) (int, error) {
// Is this username already taken..?
err := mus.usernameExists.QueryRow(username).Scan(&username)
if err != ErrNoRows {
return 0, ErrAccountExists
}
salt, err := GenerateSafeString(SaltLength)
if err != nil {
return 0, err
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password+salt), bcrypt.DefaultCost)
if err != nil {
return 0, err
}
res, err := mus.register.Exec(username, email, string(hashedPassword), salt, group, active)
if err != nil {
return 0, err
}
lastID, err := res.LastInsertId()
return int(lastID), err
}
func (mus *MemoryUserStore) Flush() {
mus.Lock()
mus.items = make(map[int]*User)
mus.length = 0
mus.Unlock()
}
// ! Is this concurrent?
// Length returns the number of users in the memory cache
func (mus *MemoryUserStore) Length() int {
return int(mus.length)
}
func (mus *MemoryUserStore) SetCapacity(capacity int) {
mus.capacity = capacity
}
func (mus *MemoryUserStore) GetCapacity() int {
return mus.capacity
}
// GlobalCount returns the total number of users registered on the forums
func (mus *MemoryUserStore) GlobalCount() (ucount int) {
err := mus.userCount.QueryRow().Scan(&ucount)
if err != nil {
LogError(err)
}
return ucount
}
type SQLUserStore struct {
get *sql.Stmt
exists *sql.Stmt
register *sql.Stmt
usernameExists *sql.Stmt
userCount *sql.Stmt
}
func NewSQLUserStore() (*SQLUserStore, error) {
acc := qgen.Builder.Accumulator()
// TODO: Add an admin version of registerStmt with more flexibility?
return &SQLUserStore{
get: acc.SimpleSelect("users", "name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid = ?", "", ""),
exists: acc.SimpleSelect("users", "uid", "uid = ?", "", ""),
register: acc.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt", "?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()"),
usernameExists: acc.SimpleSelect("users", "name", "name = ?", "", ""),
userCount: acc.SimpleCount("users", "", ""),
}, acc.FirstError()
}
func (mus *SQLUserStore) DirtyGet(id int) *User {
user := &User{ID: id, Loggedin: true}
err := mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
user.Init()
if err != nil {
return BlankUser()
}
return user
}
func (mus *SQLUserStore) Get(id int) (*User, error) {
user := &User{ID: id, Loggedin: true}
err := mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
user.Init()
return user, err
}
// TODO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts?
func (mus *SQLUserStore) BulkGetMap(ids []int) (list map[int]*User, err error) {
var qlist string
var uidList []interface{}
for _, id := range ids {
uidList = append(uidList, strconv.Itoa(id))
qlist += "?,"
}
qlist = qlist[0 : len(qlist)-1]
acc := qgen.Builder.Accumulator()
rows, err := acc.Select("users").Columns("uid, name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group").Where("uid IN(" + qlist + ")").Query(uidList...)
if err != nil {
return nil, err
}
list = make(map[int]*User)
for rows.Next() {
user := &User{Loggedin: true}
err := rows.Scan(&user.ID, &user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
if err != nil {
return nil, err
}
user.Init()
list[user.ID] = user
}
return list, nil
}
func (mus *SQLUserStore) BypassGet(id int) (*User, error) {
user := &User{ID: id, Loggedin: true}
err := mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
user.Init()
return user, err
}
func (mus *SQLUserStore) Exists(id int) bool {
err := mus.exists.QueryRow(id).Scan(&id)
if err != nil && err != ErrNoRows {
LogError(err)
}
return err != ErrNoRows
}
func (mus *SQLUserStore) Create(username string, password string, email string, group int, active bool) (int, error) {
// Is this username already taken..? // Is this username already taken..?
err := mus.usernameExists.QueryRow(username).Scan(&username) err := mus.usernameExists.QueryRow(username).Scan(&username)
if err != ErrNoRows { if err != ErrNoRows {
@ -475,7 +228,7 @@ func (mus *SQLUserStore) Create(username string, password string, email string,
} }
// GlobalCount returns the total number of users registered on the forums // GlobalCount returns the total number of users registered on the forums
func (mus *SQLUserStore) GlobalCount() (ucount int) { func (mus *DefaultUserStore) GlobalCount() (ucount int) {
err := mus.userCount.QueryRow().Scan(&ucount) err := mus.userCount.QueryRow().Scan(&ucount)
if err != nil { if err != nil {
LogError(err) LogError(err)
@ -483,54 +236,15 @@ func (mus *SQLUserStore) GlobalCount() (ucount int) {
return ucount return ucount
} }
// TODO: MockUserStore func (mus *DefaultUserStore) SetCache(cache UserCache) {
mus.cache = cache
// NullUserStore is here for tests because Go doesn't have short-circuiting
type NullUserStore struct {
} }
func (nus *NullUserStore) CacheGet(_ int) (*User, error) { // TODO: We're temporarily doing this so that you can do ucache != nil in getTopicUser. Refactor it.
return nil, ErrNoRows func (mus *DefaultUserStore) GetCache() UserCache {
_, ok := mus.cache.(*NullUserCache)
if ok {
return nil
} }
return mus.cache
func (nus *NullUserStore) CacheGetUnsafe(_ int) (*User, error) {
return nil, ErrNoRows
}
func (nus *NullUserStore) CacheSet(_ *User) error {
return ErrStoreCapacityOverflow
}
func (nus *NullUserStore) CacheAdd(_ *User) error {
return ErrStoreCapacityOverflow
}
func (nus *NullUserStore) CacheAddUnsafe(_ *User) error {
return ErrStoreCapacityOverflow
}
func (nus *NullUserStore) CacheRemove(_ int) error {
return ErrNoRows
}
func (nus *NullUserStore) CacheRemoveUnsafe(_ int) error {
return ErrNoRows
}
func (nus *NullUserStore) Flush() {
}
func (nus *NullUserStore) Reload(_ int) error {
return ErrNoRows
}
func (nus *NullUserStore) Length() int {
return 0
}
func (nus *NullUserStore) SetCapacity(_ int) {
}
func (nus *NullUserStore) GetCapacity() int {
return 0
} }

View File

@ -33,26 +33,37 @@ func init() {
} }
func LoadWordFilters() error { func LoadWordFilters() error {
rows, err := filterStmts.getWordFilters.Query() var wordFilters = WordFilterMap(make(map[int]WordFilter))
filters, err := wordFilters.BypassGetAll()
if err != nil { if err != nil {
return err return err
} }
for _, filter := range filters {
wordFilters[filter.ID] = filter
}
WordFilterBox.Store(wordFilters)
return nil
}
// TODO: Return pointers to word filters intead to save memory?
func (wBox WordFilterMap) BypassGetAll() (filters []WordFilter, err error) {
rows, err := filterStmts.getWordFilters.Query()
if err != nil {
return nil, err
}
defer rows.Close() defer rows.Close()
var wordFilters = WordFilterMap(make(map[int]WordFilter))
var wfid int
var find string
var replacement string
for rows.Next() { for rows.Next() {
err := rows.Scan(&wfid, &find, &replacement) filter := WordFilter{ID: 0}
err := rows.Scan(&filter.ID, &filter.Find, &filter.Replacement)
if err != nil { if err != nil {
return err return filters, err
} }
wordFilters[wfid] = WordFilter{ID: wfid, Find: find, Replacement: replacement} filters = append(filters, filter)
} }
WordFilterBox.Store(wordFilters) return filters, rows.Err()
return rows.Err()
} }
func AddWordFilter(id int, find string, replacement string) { func AddWordFilter(id int, find string, replacement string) {

View File

@ -35,47 +35,50 @@ func InitDatabase() (err error) {
} }
log.Print("Loading the usergroups.") log.Print("Loading the usergroups.")
common.Gstore, err = common.NewMemoryGroupStore() common.Groups, err = common.NewMemoryGroupStore()
if err != nil { if err != nil {
return err return err
} }
err2 := common.Gstore.LoadGroups() err2 := common.Groups.LoadGroups()
if err2 != nil { if err2 != nil {
return err2 return err2
} }
// We have to put this here, otherwise LoadForums() won't be able to get the last poster data when building it's forums // We have to put this here, otherwise LoadForums() won't be able to get the last poster data when building it's forums
log.Print("Initialising the user and topic stores") log.Print("Initialising the user and topic stores")
var ucache common.UserCache
var tcache common.TopicCache
if common.Config.CacheTopicUser == common.CACHE_STATIC { if common.Config.CacheTopicUser == common.CACHE_STATIC {
common.Users, err = common.NewMemoryUserStore(common.Config.UserCacheCapacity) ucache = common.NewMemoryUserCache(common.Config.UserCacheCapacity)
common.Topics, err2 = common.NewMemoryTopicStore(common.Config.TopicCacheCapacity) tcache = common.NewMemoryTopicCache(common.Config.TopicCacheCapacity)
} else {
common.Users, err = common.NewSQLUserStore()
common.Topics, err2 = common.NewSQLTopicStore()
} }
common.Users, err = common.NewDefaultUserStore(ucache)
if err != nil { if err != nil {
return err return err
} }
if err2 != nil { common.Topics, err = common.NewDefaultTopicStore(tcache)
if err != nil {
return err2 return err2
} }
log.Print("Loading the forums.") log.Print("Loading the forums.")
common.Fstore, err = common.NewMemoryForumStore() common.Forums, err = common.NewMemoryForumStore()
if err != nil { if err != nil {
return err return err
} }
err = common.Fstore.LoadForums() err = common.Forums.LoadForums()
if err != nil { if err != nil {
return err return err
} }
log.Print("Loading the forum permissions.") log.Print("Loading the forum permissions.")
common.Fpstore, err = common.NewMemoryForumPermsStore() common.FPStore, err = common.NewMemoryForumPermsStore()
if err != nil { if err != nil {
return err return err
} }
err = common.Fpstore.Init() err = common.FPStore.Init()
if err != nil { if err != nil {
return err return err
} }

View File

@ -0,0 +1,44 @@
package guilds
import "database/sql"
import "../../../query_gen/lib"
var Gstore GuildStore
type GuildStore interface {
Get(guildID int) (guild *Guild, err error)
Create(name string, desc string, active bool, privacy int, uid int, fid int) (int, error)
}
type SQLGuildStore struct {
get *sql.Stmt
create *sql.Stmt
}
func NewSQLGuildStore() (*SQLGuildStore, error) {
acc := qgen.Builder.Accumulator()
return &SQLGuildStore{
get: acc.Select("guilds").Columns("name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime").Where("guildID = ?").Prepare(),
create: acc.Insert("guilds").Columns("name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime").Fields("?,?,?,?,1,?,1,?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(),
}, acc.FirstError()
}
func (store *SQLGuildStore) Close() {
_ = store.get.Close()
_ = store.create.Close()
}
func (store *SQLGuildStore) Get(guildID int) (guild *Guild, err error) {
guild = &Guild{ID: guildID}
err = store.get.QueryRow(guildID).Scan(&guild.Name, &guild.Desc, &guild.Active, &guild.Privacy, &guild.Joinable, &guild.Owner, &guild.MemberCount, &guild.MainForumID, &guild.Backdrop, &guild.CreatedAt, &guild.LastUpdateTime)
return guild, err
}
func (store *SQLGuildStore) Create(name string, desc string, active bool, privacy int, uid int, fid int) (int, error) {
res, err := store.create.Exec(name, desc, active, privacy, uid, fid)
if err != nil {
return 0, err
}
lastID, err := res.LastInsertId()
return int(lastID), err
}

View File

@ -21,8 +21,6 @@ var ListStmt *sql.Stmt
var MemberListStmt *sql.Stmt var MemberListStmt *sql.Stmt
var MemberListJoinStmt *sql.Stmt var MemberListJoinStmt *sql.Stmt
var GetMemberStmt *sql.Stmt var GetMemberStmt *sql.Stmt
var GetGuildStmt *sql.Stmt
var CreateGuildStmt *sql.Stmt
var AttachForumStmt *sql.Stmt var AttachForumStmt *sql.Stmt
var UnattachForumStmt *sql.Stmt var UnattachForumStmt *sql.Stmt
var AddMemberStmt *sql.Stmt var AddMemberStmt *sql.Stmt
@ -105,8 +103,8 @@ func PrebuildTmplList(user common.User, headerVars *common.HeaderVars) common.CT
CreatedAt: "date", CreatedAt: "date",
LastUpdateTime: "date", LastUpdateTime: "date",
MainForumID: 1, MainForumID: 1,
MainForum: common.Fstore.DirtyGet(1), MainForum: common.Forums.DirtyGet(1),
Forums: []*common.Forum{common.Fstore.DirtyGet(1)}, Forums: []*common.Forum{common.Forums.DirtyGet(1)},
}, },
} }
listPage := ListPage{"Guild List", user, headerVars, guildList} listPage := ListPage{"Guild List", user, headerVars, guildList}
@ -127,9 +125,9 @@ func CommonAreaWidgets(headerVars *common.HeaderVars) {
return return
} }
if common.Themes[headerVars.ThemeName].Sidebars == "left" { if common.Themes[headerVars.Theme.Name].Sidebars == "left" {
headerVars.Widgets.LeftSidebar = template.HTML(string(b.Bytes())) headerVars.Widgets.LeftSidebar = template.HTML(string(b.Bytes()))
} else if common.Themes[headerVars.ThemeName].Sidebars == "right" || common.Themes[headerVars.ThemeName].Sidebars == "both" { } else if common.Themes[headerVars.Theme.Name].Sidebars == "right" || common.Themes[headerVars.Theme.Name].Sidebars == "both" {
headerVars.Widgets.RightSidebar = template.HTML(string(b.Bytes())) headerVars.Widgets.RightSidebar = template.HTML(string(b.Bytes()))
} }
} }
@ -151,9 +149,9 @@ func GuildWidgets(headerVars *common.HeaderVars, guildItem *Guild) (success bool
return false return false
} }
if themes[headerVars.ThemeName].Sidebars == "left" { if themes[headerVars.Theme.Name].Sidebars == "left" {
headerVars.Widgets.LeftSidebar = template.HTML(string(b.Bytes())) headerVars.Widgets.LeftSidebar = template.HTML(string(b.Bytes()))
} else if themes[headerVars.ThemeName].Sidebars == "right" || themes[headerVars.ThemeName].Sidebars == "both" { } else if themes[headerVars.Theme.Name].Sidebars == "right" || themes[headerVars.Theme.Name].Sidebars == "both" {
headerVars.Widgets.RightSidebar = template.HTML(string(b.Bytes())) headerVars.Widgets.RightSidebar = template.HTML(string(b.Bytes()))
} else { } else {
return false return false
@ -194,19 +192,13 @@ func RouteGuildList(w http.ResponseWriter, r *http.Request, user common.User) co
} }
pi := ListPage{"Guild List", user, headerVars, guildList} pi := ListPage{"Guild List", user, headerVars, guildList}
err = common.RunThemeTemplate(headerVars.ThemeName, "guilds_guild_list", pi, w) err = common.RunThemeTemplate(headerVars.Theme.Name, "guilds_guild_list", pi, w)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
return nil return nil
} }
func GetGuild(guildID int) (guildItem *Guild, err error) {
guildItem = &Guild{ID: guildID}
err = GetGuildStmt.QueryRow(guildID).Scan(&guildItem.Name, &guildItem.Desc, &guildItem.Active, &guildItem.Privacy, &guildItem.Joinable, &guildItem.Owner, &guildItem.MemberCount, &guildItem.MainForumID, &guildItem.Backdrop, &guildItem.CreatedAt, &guildItem.LastUpdateTime)
return guildItem, err
}
func MiddleViewGuild(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func MiddleViewGuild(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
// SEO URLs... // SEO URLs...
halves := strings.Split(r.URL.Path[len("/guild/"):], ".") halves := strings.Split(r.URL.Path[len("/guild/"):], ".")
@ -218,7 +210,7 @@ func MiddleViewGuild(w http.ResponseWriter, r *http.Request, user common.User) c
return common.PreError("Not a valid guild ID", w, r) return common.PreError("Not a valid guild ID", w, r)
} }
guildItem, err := GetGuild(guildID) guildItem, err := Gstore.Get(guildID)
if err != nil { if err != nil {
return common.LocalError("Bad guild", w, r, user) return common.LocalError("Bad guild", w, r, user)
} }
@ -277,32 +269,28 @@ func RouteCreateGuildSubmit(w http.ResponseWriter, r *http.Request, user common.
} }
// Create the backing forum // Create the backing forum
fid, err := common.Fstore.Create(guildName, "", true, "") fid, err := common.Forums.Create(guildName, "", true, "")
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
res, err := CreateGuildStmt.Exec(guildName, guildDesc, guildActive, guildPrivacy, user.ID, fid) gid, err := Gstore.Create(guildName, guildDesc, guildActive, guildPrivacy, user.ID, fid)
if err != nil {
return common.InternalError(err, w, r)
}
lastID, err := res.LastInsertId()
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
// Add the main backing forum to the forum list // Add the main backing forum to the forum list
err = AttachForum(int(lastID), fid) err = AttachForum(gid, fid)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
_, err = AddMemberStmt.Exec(lastID, user.ID, 2) _, err = AddMemberStmt.Exec(gid, user.ID, 2)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
http.Redirect(w, r, BuildGuildURL(common.NameToSlug(guildName), int(lastID)), http.StatusSeeOther) http.Redirect(w, r, BuildGuildURL(common.NameToSlug(guildName), gid), http.StatusSeeOther)
return nil return nil
} }
@ -322,9 +310,7 @@ func RouteMemberList(w http.ResponseWriter, r *http.Request, user common.User) c
return common.PreError("Not a valid group ID", w, r) return common.PreError("Not a valid group ID", w, r)
} }
var guildItem = &Guild{ID: guildID} guildItem, err := Gstore.Get(guildID)
var mainForum int // Unused
err = GetGuildStmt.QueryRow(guildID).Scan(&guildItem.Name, &guildItem.Desc, &guildItem.Active, &guildItem.Privacy, &guildItem.Joinable, &guildItem.Owner, &guildItem.MemberCount, &mainForum, &guildItem.Backdrop, &guildItem.CreatedAt, &guildItem.LastUpdateTime)
if err != nil { if err != nil {
return common.LocalError("Bad group", w, r, user) return common.LocalError("Bad group", w, r, user)
} }
@ -380,7 +366,7 @@ func RouteMemberList(w http.ResponseWriter, r *http.Request, user common.User) c
return nil return nil
} }
} }
err = common.RunThemeTemplate(headerVars.ThemeName, "guilds_member_list", pi, w) err = common.RunThemeTemplate(headerVars.Theme.Name, "guilds_member_list", pi, w)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -439,7 +425,7 @@ func TrowAssign(args ...interface{}) interface{} {
// TODO: It would be nice, if you could select one of the boards in the group from that drop-down rather than just the one you got linked from // TODO: It would be nice, if you could select one of the boards in the group from that drop-down rather than just the one you got linked from
func TopicCreatePreLoop(args ...interface{}) interface{} { func TopicCreatePreLoop(args ...interface{}) interface{} {
var fid = args[2].(int) var fid = args[2].(int)
if common.Fstore.DirtyGet(fid).ParentType == "guild" { if common.Forums.DirtyGet(fid).ParentType == "guild" {
var strictmode = args[5].(*bool) var strictmode = args[5].(*bool)
*strictmode = true *strictmode = true
} }
@ -452,14 +438,14 @@ func TopicCreatePreLoop(args ...interface{}) interface{} {
func ForumCheck(args ...interface{}) (skip bool, rerr common.RouteError) { func ForumCheck(args ...interface{}) (skip bool, rerr common.RouteError) {
var r = args[1].(*http.Request) var r = args[1].(*http.Request)
var fid = args[3].(*int) var fid = args[3].(*int)
var forum = common.Fstore.DirtyGet(*fid) var forum = common.Forums.DirtyGet(*fid)
if forum.ParentType == "guild" { if forum.ParentType == "guild" {
var err error var err error
var w = args[0].(http.ResponseWriter) var w = args[0].(http.ResponseWriter)
guildItem, ok := r.Context().Value("guilds_current_group").(*Guild) guildItem, ok := r.Context().Value("guilds_current_group").(*Guild)
if !ok { if !ok {
guildItem, err = GetGuild(forum.ParentID) guildItem, err = Gstore.Get(forum.ParentID)
if err != nil { if err != nil {
return true, common.InternalError(errors.New("Unable to find the parent group for a forum"), w, r) return true, common.InternalError(errors.New("Unable to find the parent group for a forum"), w, r)
} }

View File

@ -10,7 +10,6 @@ import "./common"
// nolint // nolint
type Stmts struct { type Stmts struct {
getPassword *sql.Stmt getPassword *sql.Stmt
getSettings *sql.Stmt
isPluginActive *sql.Stmt isPluginActive *sql.Stmt
getUsersOffset *sql.Stmt getUsersOffset *sql.Stmt
isThemeDefault *sql.Stmt isThemeDefault *sql.Stmt
@ -45,7 +44,6 @@ type Stmts struct {
createWordFilter *sql.Stmt createWordFilter *sql.Stmt
editReply *sql.Stmt editReply *sql.Stmt
editProfileReply *sql.Stmt editProfileReply *sql.Stmt
updateSetting *sql.Stmt
updatePlugin *sql.Stmt updatePlugin *sql.Stmt
updatePluginInstall *sql.Stmt updatePluginInstall *sql.Stmt
updateTheme *sql.Stmt updateTheme *sql.Stmt
@ -61,7 +59,6 @@ type Stmts struct {
deleteActivityStreamMatch *sql.Stmt deleteActivityStreamMatch *sql.Stmt
deleteWordFilter *sql.Stmt deleteWordFilter *sql.Stmt
reportExists *sql.Stmt reportExists *sql.Stmt
modlogCount *sql.Stmt
notifyWatchers *sql.Stmt notifyWatchers *sql.Stmt
getActivityFeedByWatcher *sql.Stmt getActivityFeedByWatcher *sql.Stmt
@ -90,13 +87,6 @@ func _gen_mssql() (err error) {
return err return err
} }
log.Print("Preparing getSettings statement.")
stmts.getSettings, err = db.Prepare("SELECT [name],[content],[type] FROM [settings]")
if err != nil {
log.Print("Bad Query: ","SELECT [name],[content],[type] FROM [settings]")
return err
}
log.Print("Preparing isPluginActive statement.") log.Print("Preparing isPluginActive statement.")
stmts.isPluginActive, err = db.Prepare("SELECT [active] FROM [plugins] WHERE [uname] = ?1") stmts.isPluginActive, err = db.Prepare("SELECT [active] FROM [plugins] WHERE [uname] = ?1")
if err != nil { if err != nil {
@ -335,13 +325,6 @@ func _gen_mssql() (err error) {
return err return err
} }
log.Print("Preparing updateSetting statement.")
stmts.updateSetting, err = db.Prepare("UPDATE [settings] SET [content] = ? WHERE [name] = ?")
if err != nil {
log.Print("Bad Query: ","UPDATE [settings] SET [content] = ? WHERE [name] = ?")
return err
}
log.Print("Preparing updatePlugin statement.") log.Print("Preparing updatePlugin statement.")
stmts.updatePlugin, err = db.Prepare("UPDATE [plugins] SET [active] = ? WHERE [uname] = ?") stmts.updatePlugin, err = db.Prepare("UPDATE [plugins] SET [active] = ? WHERE [uname] = ?")
if err != nil { if err != nil {
@ -447,13 +430,6 @@ func _gen_mssql() (err error) {
return err return err
} }
log.Print("Preparing modlogCount statement.")
stmts.modlogCount, err = db.Prepare("SELECT COUNT(*) AS [count] FROM [moderation_logs]")
if err != nil {
log.Print("Bad Query: ","SELECT COUNT(*) AS [count] FROM [moderation_logs]")
return err
}
log.Print("Preparing notifyWatchers statement.") log.Print("Preparing notifyWatchers statement.")
stmts.notifyWatchers, err = db.Prepare("INSERT INTO [activity_stream_matches] ([watcher],[asid]) SELECT [activity_subscriptions].[user],[activity_stream].[asid] FROM [activity_stream] INNER JOIN [activity_subscriptions] ON [activity_subscriptions].[targetType] = [activity_stream].[elementType] AND [activity_subscriptions].[targetID] = [activity_stream].[elementID] AND [activity_subscriptions].[user] != [activity_stream].[actor] WHERE [asid] = ?1") stmts.notifyWatchers, err = db.Prepare("INSERT INTO [activity_stream_matches] ([watcher],[asid]) SELECT [activity_subscriptions].[user],[activity_stream].[asid] FROM [activity_stream] INNER JOIN [activity_subscriptions] ON [activity_subscriptions].[targetType] = [activity_stream].[elementType] AND [activity_subscriptions].[targetID] = [activity_stream].[elementID] AND [activity_subscriptions].[user] != [activity_stream].[actor] WHERE [asid] = ?1")
if err != nil { if err != nil {

View File

@ -12,7 +12,6 @@ import "./common"
// nolint // nolint
type Stmts struct { type Stmts struct {
getPassword *sql.Stmt getPassword *sql.Stmt
getSettings *sql.Stmt
isPluginActive *sql.Stmt isPluginActive *sql.Stmt
getUsersOffset *sql.Stmt getUsersOffset *sql.Stmt
isThemeDefault *sql.Stmt isThemeDefault *sql.Stmt
@ -47,7 +46,6 @@ type Stmts struct {
createWordFilter *sql.Stmt createWordFilter *sql.Stmt
editReply *sql.Stmt editReply *sql.Stmt
editProfileReply *sql.Stmt editProfileReply *sql.Stmt
updateSetting *sql.Stmt
updatePlugin *sql.Stmt updatePlugin *sql.Stmt
updatePluginInstall *sql.Stmt updatePluginInstall *sql.Stmt
updateTheme *sql.Stmt updateTheme *sql.Stmt
@ -63,7 +61,6 @@ type Stmts struct {
deleteActivityStreamMatch *sql.Stmt deleteActivityStreamMatch *sql.Stmt
deleteWordFilter *sql.Stmt deleteWordFilter *sql.Stmt
reportExists *sql.Stmt reportExists *sql.Stmt
modlogCount *sql.Stmt
notifyWatchers *sql.Stmt notifyWatchers *sql.Stmt
getActivityFeedByWatcher *sql.Stmt getActivityFeedByWatcher *sql.Stmt
@ -91,12 +88,6 @@ func _gen_mysql() (err error) {
return err return err
} }
log.Print("Preparing getSettings statement.")
stmts.getSettings, err = db.Prepare("SELECT `name`,`content`,`type` FROM `settings`")
if err != nil {
return err
}
log.Print("Preparing isPluginActive statement.") log.Print("Preparing isPluginActive statement.")
stmts.isPluginActive, err = db.Prepare("SELECT `active` FROM `plugins` WHERE `uname` = ?") stmts.isPluginActive, err = db.Prepare("SELECT `active` FROM `plugins` WHERE `uname` = ?")
if err != nil { if err != nil {
@ -301,12 +292,6 @@ func _gen_mysql() (err error) {
return err return err
} }
log.Print("Preparing updateSetting statement.")
stmts.updateSetting, err = db.Prepare("UPDATE `settings` SET `content` = ? WHERE `name` = ?")
if err != nil {
return err
}
log.Print("Preparing updatePlugin statement.") log.Print("Preparing updatePlugin statement.")
stmts.updatePlugin, err = db.Prepare("UPDATE `plugins` SET `active` = ? WHERE `uname` = ?") stmts.updatePlugin, err = db.Prepare("UPDATE `plugins` SET `active` = ? WHERE `uname` = ?")
if err != nil { if err != nil {
@ -397,12 +382,6 @@ func _gen_mysql() (err error) {
return err return err
} }
log.Print("Preparing modlogCount statement.")
stmts.modlogCount, err = db.Prepare("SELECT COUNT(*) AS `count` FROM `moderation_logs`")
if err != nil {
return err
}
log.Print("Preparing notifyWatchers statement.") log.Print("Preparing notifyWatchers statement.")
stmts.notifyWatchers, err = db.Prepare("INSERT INTO `activity_stream_matches`(`watcher`,`asid`) SELECT `activity_subscriptions`.`user`, `activity_stream`.`asid` FROM `activity_stream` INNER JOIN `activity_subscriptions` ON `activity_subscriptions`.`targetType` = `activity_stream`.`elementType` AND `activity_subscriptions`.`targetID` = `activity_stream`.`elementID` AND `activity_subscriptions`.`user` != `activity_stream`.`actor` WHERE `asid` = ?") stmts.notifyWatchers, err = db.Prepare("INSERT INTO `activity_stream_matches`(`watcher`,`asid`) SELECT `activity_subscriptions`.`user`, `activity_stream`.`asid` FROM `activity_stream` INNER JOIN `activity_subscriptions` ON `activity_subscriptions`.`targetType` = `activity_stream`.`elementType` AND `activity_subscriptions`.`targetID` = `activity_stream`.`elementID` AND `activity_subscriptions`.`user` != `activity_stream`.`actor` WHERE `asid` = ?")
if err != nil { if err != nil {

View File

@ -11,7 +11,6 @@ import "./common"
type Stmts struct { type Stmts struct {
editReply *sql.Stmt editReply *sql.Stmt
editProfileReply *sql.Stmt editProfileReply *sql.Stmt
updateSetting *sql.Stmt
updatePlugin *sql.Stmt updatePlugin *sql.Stmt
updatePluginInstall *sql.Stmt updatePluginInstall *sql.Stmt
updateTheme *sql.Stmt updateTheme *sql.Stmt
@ -55,12 +54,6 @@ func _gen_pgsql() (err error) {
return err return err
} }
log.Print("Preparing updateSetting statement.")
stmts.updateSetting, err = db.Prepare("UPDATE `settings` SET `content` = ? WHERE `name` = ?")
if err != nil {
return err
}
log.Print("Preparing updatePlugin statement.") log.Print("Preparing updatePlugin statement.")
stmts.updatePlugin, err = db.Prepare("UPDATE `plugins` SET `active` = ? WHERE `uname` = ?") stmts.updatePlugin, err = db.Prepare("UPDATE `plugins` SET `active` = ? WHERE `uname` = ?")
if err != nil { if err != nil {

View File

@ -246,7 +246,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
case "/panel/settings/": case "/panel/settings/":
err = routePanelSettings(w,req,user) err = routePanelSettings(w,req,user)
case "/panel/settings/edit/": case "/panel/settings/edit/":
err = routePanelSetting(w,req,user,extra_data) err = routePanelSettingEdit(w,req,user,extra_data)
case "/panel/settings/edit/submit/": case "/panel/settings/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -254,7 +254,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
err = routePanelSettingEdit(w,req,user,extra_data) err = routePanelSettingEditSubmit(w,req,user,extra_data)
case "/panel/settings/word-filters/": case "/panel/settings/word-filters/":
err = routePanelWordFilters(w,req,user) err = routePanelWordFilters(w,req,user)
case "/panel/settings/word-filters/create/": case "/panel/settings/word-filters/create/":

View File

@ -15,7 +15,6 @@ import (
"./common" "./common"
"./install/install" "./install/install"
"./query_gen/lib" "./query_gen/lib"
//"runtime/pprof"
//"github.com/husobee/vestigo" //"github.com/husobee/vestigo"
) )
@ -77,44 +76,7 @@ func gloinit() (err error) {
return err return err
} }
common.Rstore, err = common.NewSQLReplyStore() err = afterDBInit()
if err != nil {
return err
}
common.Prstore, err = common.NewSQLProfileReplyStore()
if err != nil {
return err
}
dbProd = db
//db_test, err = sql.Open("testdb","")
//if err != nil {
// return err
//}
err = common.InitTemplates()
if err != nil {
return err
}
dbProd.SetMaxOpenConns(64)
err = common.InitPhrases()
if err != nil {
log.Fatal(err)
}
log.Print("Loading the static files.")
err = common.StaticFiles.Init()
if err != nil {
return err
}
common.Auth, err = common.NewDefaultAuth()
if err != nil {
return err
}
err = common.LoadWordFilters()
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,7 +1,6 @@
/* /*
* *
* Gosora MSSQL Interface * Gosora MSSQL Interface
* Under heavy development
* Copyright Azareal 2017 - 2018 * Copyright Azareal 2017 - 2018
* *
*/ */
@ -80,14 +79,9 @@ func (ins *MssqlInstaller) InitDatabase() (err error) {
// TODO: Create the database, if it doesn't exist // TODO: Create the database, if it doesn't exist
// Ready the query builder // Ready the query builder
qgen.Builder.SetConn(db)
err = qgen.Builder.SetAdapter("mssql")
if err != nil {
return err
}
ins.db = db ins.db = db
qgen.Builder.SetConn(db)
return nil return qgen.Builder.SetAdapter("mssql")
} }
func (ins *MssqlInstaller) TableDefs() (err error) { func (ins *MssqlInstaller) TableDefs() (err error) {
@ -126,7 +120,6 @@ func (ins *MssqlInstaller) TableDefs() (err error) {
return err return err
} }
} }
//fmt.Println("Finished creating the tables")
return nil return nil
} }
@ -150,8 +143,6 @@ func (ins *MssqlInstaller) InitialData() (err error) {
return err return err
} }
} }
//fmt.Println("Finished inserting the database data")
return nil return nil
} }

View File

@ -55,5 +55,36 @@
"Accounts": { "Accounts": {
"VerifyEmailSubject": "Validate Your Email @ {{name}}", "VerifyEmailSubject": "Validate Your Email @ {{name}}",
"VerifyEmailBody": "Dear {{username}}, following your registration on our forums, we ask you to validate your email, so that we can confirm that this email actually belongs to you.\n\nClick on the following link to do so. {{schema}}://{{url}}/user/edit/token/{{token}}\n\nIf you haven't created an account here, then please feel free to ignore this email.\nWe're sorry for the inconvenience this may have caused." "VerifyEmailBody": "Dear {{username}}, following your registration on our forums, we ask you to validate your email, so that we can confirm that this email actually belongs to you.\n\nClick on the following link to do so. {{schema}}://{{url}}/user/edit/token/{{token}}\n\nIf you haven't created an account here, then please feel free to ignore this email.\nWe're sorry for the inconvenience this may have caused."
},
"Errors": {
"NoPerms": {
}
},
"PageTitles": {
"overview":"Overview",
"page":"Page",
"topics":"All Topics",
"forums":"Forum List",
"login":"Login",
"register":"Registration",
"ip-search":"IP Search",
"panel-dashboard":"Control Panel Dashboard",
"panel-forums":"Forum Manager",
"panel-delete-forum":"Delete Forum",
"panel-edit-forum":"Forum Editor",
"panel-settings":"Setting Manager",
"panel-edit-setting":"Edit Setting",
"panel-word-filters":"Word Filter Manager",
"panel-edit-word-filter":"Edit Word Filter",
"panel-plugins":"Plugin Manager",
"panel-users":"User Manager",
"panel-edit-user":"User Editor",
"panel-groups":"Group Manager",
"panel-edit-group":"Group Editor",
"panel-themes":"Theme Manager",
"panel-backups":"Backups",
"panel-mod-logs":"Moderation Logs",
"panel-debug":"Debug"
} }
} }

94
main.go
View File

@ -29,6 +29,61 @@ type Globs struct {
stmts *Stmts stmts *Stmts
} }
func afterDBInit() (err error) {
common.Rstore, err = common.NewSQLReplyStore()
if err != nil {
return err
}
common.Prstore, err = common.NewSQLProfileReplyStore()
if err != nil {
return err
}
err = common.InitTemplates()
if err != nil {
return err
}
err = common.InitPhrases()
if err != nil {
return err
}
log.Print("Loading the static files.")
err = common.StaticFiles.Init()
if err != nil {
return err
}
log.Print("Initialising the widgets")
err = common.InitWidgets()
if err != nil {
return err
}
log.Print("Initialising the authentication system")
common.Auth, err = common.NewDefaultAuth()
if err != nil {
return err
}
err = common.LoadWordFilters()
if err != nil {
return err
}
common.ModLogs, err = common.NewModLogStore()
if err != nil {
return err
}
common.AdminLogs, err = common.NewAdminLogStore()
if err != nil {
return err
}
return nil
}
// TODO: Split this function up // TODO: Split this function up
func main() { func main() {
// TODO: Recover from panics // TODO: Recover from panics
@ -90,44 +145,7 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
common.Rstore, err = common.NewSQLReplyStore() err = afterDBInit()
if err != nil {
log.Fatal(err)
}
common.Prstore, err = common.NewSQLProfileReplyStore()
if err != nil {
log.Fatal(err)
}
err = common.InitTemplates()
if err != nil {
log.Fatal(err)
}
err = common.InitPhrases()
if err != nil {
log.Fatal(err)
}
log.Print("Loading the static files.")
err = common.StaticFiles.Init()
if err != nil {
log.Fatal(err)
}
log.Print("Initialising the widgets")
err = common.InitWidgets()
if err != nil {
log.Fatal(err)
}
log.Print("Initialising the authentication system")
common.Auth, err = common.NewDefaultAuth()
if err != nil {
log.Fatal(err)
}
err = common.LoadWordFilters()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -55,12 +55,12 @@ func routeTopicCreate(w http.ResponseWriter, r *http.Request, user common.User,
var forumList []common.Forum var forumList []common.Forum
var canSee []int var canSee []int
if user.IsSuperAdmin { if user.IsSuperAdmin {
canSee, err = common.Fstore.GetAllVisibleIDs() canSee, err = common.Forums.GetAllVisibleIDs()
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
} else { } else {
group, err := common.Gstore.Get(user.Group) group, err := common.Groups.Get(user.Group)
if err != nil { if err != nil {
// TODO: Refactor this // TODO: Refactor this
common.LocalError("Something weird happened behind the scenes", w, r, user) common.LocalError("Something weird happened behind the scenes", w, r, user)
@ -78,7 +78,7 @@ func routeTopicCreate(w http.ResponseWriter, r *http.Request, user common.User,
} }
// Do a bulk forum fetch, just in case it's the SqlForumStore? // Do a bulk forum fetch, just in case it's the SqlForumStore?
forum := common.Fstore.DirtyGet(ffid) forum := common.Forums.DirtyGet(ffid)
if forum.Name != "" && forum.Active { if forum.Name != "" && forum.Active {
fcopy := forum.Copy() fcopy := forum.Copy()
if common.Hooks["topic_create_frow_assign"] != nil { if common.Hooks["topic_create_frow_assign"] != nil {
@ -98,7 +98,7 @@ func routeTopicCreate(w http.ResponseWriter, r *http.Request, user common.User,
} }
} }
err = common.RunThemeTemplate(headerVars.ThemeName, "create-topic", ctpage, w) err = common.RunThemeTemplate(headerVars.Theme.Name, "create-topic", ctpage, w)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -340,7 +340,7 @@ func routeCreateReply(w http.ResponseWriter, r *http.Request, user common.User)
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
err = common.Fstore.UpdateLastTopic(tid, user.ID, topic.ParentID) err = common.Forums.UpdateLastTopic(tid, user.ID, topic.ParentID)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -623,7 +623,7 @@ func routeReportSubmit(w http.ResponseWriter, r *http.Request, user common.User,
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
err = common.Fstore.AddTopic(int(lastID), user.ID, fid) err = common.Forums.AddTopic(int(lastID), user.ID, fid)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }

1
migrations/filler.txt Normal file
View File

@ -0,0 +1 @@
This file is here so that Git will include this folder in the repository.

View File

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"net/http/httptest" "net/http/httptest"
"runtime/debug" "runtime/debug"
"strconv"
"testing" "testing"
"time" "time"
@ -40,29 +39,39 @@ func TestUserStore(t *testing.T) {
} }
var err error var err error
common.Users, err = common.NewMemoryUserStore(common.Config.UserCacheCapacity) ucache := common.NewMemoryUserCache(common.Config.UserCacheCapacity)
common.Users, err = common.NewDefaultUserStore(ucache)
expectNilErr(t, err) expectNilErr(t, err)
common.Users.(common.UserCache).Flush() ucache.Flush()
userStoreTest(t, 2) userStoreTest(t, 2)
common.Users, err = common.NewSQLUserStore() common.Users, err = common.NewDefaultUserStore(nil)
expectNilErr(t, err) expectNilErr(t, err)
userStoreTest(t, 3) userStoreTest(t, 3)
} }
func userStoreTest(t *testing.T, newUserID int) { func userStoreTest(t *testing.T, newUserID int) {
ucache, hasCache := common.Users.(common.UserCache) ucache := common.Users.GetCache()
// Go doesn't have short-circuiting, so this'll allow us to do one liner tests // Go doesn't have short-circuiting, so this'll allow us to do one liner tests
if !hasCache { isCacheLengthZero := func(ucache common.UserCache) bool {
ucache = &common.NullUserStore{} if ucache == nil {
return true
} }
expect(t, (!hasCache || ucache.Length() == 0), fmt.Sprintf("The initial ucache length should be zero, not %d", ucache.Length())) return ucache.Length() == 0
}
cacheLength := func(ucache common.UserCache) int {
if ucache == nil {
return 0
}
return ucache.Length()
}
expect(t, isCacheLengthZero(ucache), fmt.Sprintf("The initial ucache length should be zero, not %d", cacheLength(ucache)))
_, err := common.Users.Get(-1) _, err := common.Users.Get(-1)
recordMustNotExist(t, err, "UID #-1 shouldn't exist") recordMustNotExist(t, err, "UID #-1 shouldn't exist")
expect(t, !hasCache || ucache.Length() == 0, fmt.Sprintf("We found %d items in the user cache and it's supposed to be empty", ucache.Length())) expect(t, isCacheLengthZero(ucache), fmt.Sprintf("We found %d items in the user cache and it's supposed to be empty", cacheLength(ucache)))
_, err = common.Users.Get(0) _, err = common.Users.Get(0)
recordMustNotExist(t, err, "UID #0 shouldn't exist") recordMustNotExist(t, err, "UID #0 shouldn't exist")
expect(t, !hasCache || ucache.Length() == 0, fmt.Sprintf("We found %d items in the user cache and it's supposed to be empty", ucache.Length())) expect(t, isCacheLengthZero(ucache), fmt.Sprintf("We found %d items in the user cache and it's supposed to be empty", cacheLength(ucache)))
user, err := common.Users.Get(1) user, err := common.Users.Get(1)
recordMustExist(t, err, "Couldn't find UID #1") recordMustExist(t, err, "Couldn't find UID #1")
@ -79,24 +88,20 @@ func userStoreTest(t *testing.T, newUserID int) {
_, err = common.Users.Get(newUserID) _, err = common.Users.Get(newUserID)
recordMustNotExist(t, err, fmt.Sprintf("UID #%d shouldn't exist", newUserID)) recordMustNotExist(t, err, fmt.Sprintf("UID #%d shouldn't exist", newUserID))
if hasCache { if ucache != nil {
expectIntToBeX(t, ucache.Length(), 1, "User cache length should be 1, not %d") expectIntToBeX(t, ucache.Length(), 1, "User cache length should be 1, not %d")
_, err = ucache.CacheGet(-1) _, err = ucache.Get(-1)
recordMustNotExist(t, err, "UID #-1 shouldn't exist, even in the cache") recordMustNotExist(t, err, "UID #-1 shouldn't exist, even in the cache")
_, err = ucache.CacheGet(0) _, err = ucache.Get(0)
recordMustNotExist(t, err, "UID #0 shouldn't exist, even in the cache") recordMustNotExist(t, err, "UID #0 shouldn't exist, even in the cache")
user, err = ucache.CacheGet(1) user, err = ucache.Get(1)
recordMustExist(t, err, "Couldn't find UID #1 in the cache") recordMustExist(t, err, "Couldn't find UID #1 in the cache")
if user.ID != 1 { expect(t, user.ID == 1, fmt.Sprintf("user.ID does not match the requested UID. Got '%d' instead.", user.ID))
t.Error("user.ID does not match the requested UID. Got '" + strconv.Itoa(user.ID) + "' instead.") expect(t, user.Name == "Admin", fmt.Sprintf("user.Name should be 'Admin', not '%s'", user.Name))
}
if user.Name != "Admin" {
t.Error("user.Name should be 'Admin', not '" + user.Name + "'")
}
_, err = ucache.CacheGet(newUserID) _, err = ucache.Get(newUserID)
recordMustNotExist(t, err, "UID #%d shouldn't exist, even in the cache", newUserID) recordMustNotExist(t, err, "UID #%d shouldn't exist, even in the cache", newUserID)
ucache.Flush() ucache.Flush()
@ -106,22 +111,12 @@ func userStoreTest(t *testing.T, newUserID int) {
// TODO: Lock onto the specific error type. Is this even possible without sacrificing the detailed information in the error message? // TODO: Lock onto the specific error type. Is this even possible without sacrificing the detailed information in the error message?
var userList map[int]*common.User var userList map[int]*common.User
userList, _ = common.Users.BulkGetMap([]int{-1}) userList, _ = common.Users.BulkGetMap([]int{-1})
if len(userList) > 0 { expect(t, len(userList) == 0, fmt.Sprintf("The userList length should be 0, not %d", len(userList)))
t.Error("There shouldn't be any results for UID #-1") expect(t, isCacheLengthZero(ucache), fmt.Sprintf("User cache length should be 0, not %d", cacheLength(ucache)))
}
if hasCache {
expectIntToBeX(t, ucache.Length(), 0, "User cache length should be 0, not %d")
}
userList, _ = common.Users.BulkGetMap([]int{0}) userList, _ = common.Users.BulkGetMap([]int{0})
if len(userList) > 0 { expect(t, len(userList) == 0, fmt.Sprintf("The userList length should be 0, not %d", len(userList)))
t.Error("There shouldn't be any results for UID #0") expect(t, isCacheLengthZero(ucache), fmt.Sprintf("User cache length should be 0, not %d", cacheLength(ucache)))
}
if hasCache {
expectIntToBeX(t, ucache.Length(), 0, "User cache length should be 0, not %d")
}
userList, _ = common.Users.BulkGetMap([]int{1}) userList, _ = common.Users.BulkGetMap([]int{1})
if len(userList) == 0 { if len(userList) == 0 {
@ -135,18 +130,14 @@ func userStoreTest(t *testing.T, newUserID int) {
t.Error("We couldn't find UID #1 in the returned map") t.Error("We couldn't find UID #1 in the returned map")
t.Error("userList", userList) t.Error("userList", userList)
} }
if user.ID != 1 { expect(t, user.ID == 1, fmt.Sprintf("user.ID does not match the requested UID. Got '%d' instead.", user.ID))
t.Error("user.ID does not match the requested UID. Got '" + strconv.Itoa(user.ID) + "' instead.")
}
if hasCache { if ucache != nil {
expectIntToBeX(t, ucache.Length(), 1, "User cache length should be 1, not %d") expectIntToBeX(t, ucache.Length(), 1, "User cache length should be 1, not %d")
user, err = ucache.CacheGet(1) user, err = ucache.Get(1)
recordMustExist(t, err, "Couldn't find UID #1 in the cache") recordMustExist(t, err, "Couldn't find UID #1 in the cache")
if user.ID != 1 { expect(t, user.ID == 1, fmt.Sprintf("user.ID does not match the requested UID. Got '%d' instead.", user.ID))
t.Errorf("user.ID does not match the requested UID. Got '%d' instead.", user.ID)
}
ucache.Flush() ucache.Flush()
} }
@ -155,7 +146,7 @@ func userStoreTest(t *testing.T, newUserID int) {
expect(t, common.Users.Exists(1), "UID #1 should exist") expect(t, common.Users.Exists(1), "UID #1 should exist")
expect(t, !common.Users.Exists(newUserID), fmt.Sprintf("UID #%d shouldn't exist", newUserID)) expect(t, !common.Users.Exists(newUserID), fmt.Sprintf("UID #%d shouldn't exist", newUserID))
expect(t, !hasCache || ucache.Length() == 0, fmt.Sprintf("User cache length should be 0, not %d", ucache.Length())) expect(t, isCacheLengthZero(ucache), fmt.Sprintf("User cache length should be 0, not %d", cacheLength(ucache)))
expectIntToBeX(t, common.Users.GlobalCount(), 1, "The number of users should be one, not %d") expectIntToBeX(t, common.Users.GlobalCount(), 1, "The number of users should be one, not %d")
var awaitingActivation = 5 var awaitingActivation = 5
@ -166,9 +157,7 @@ func userStoreTest(t *testing.T, newUserID int) {
user, err = common.Users.Get(newUserID) user, err = common.Users.Get(newUserID)
recordMustExist(t, err, "Couldn't find UID #%d", newUserID) recordMustExist(t, err, "Couldn't find UID #%d", newUserID)
if user.ID != newUserID { expect(t, user.ID == newUserID, fmt.Sprintf("The UID of the user record should be %d", newUserID))
t.Errorf("The UID of the user record should be %d", newUserID)
}
expect(t, user.Name == "Sam", "The user should be named Sam") expect(t, user.Name == "Sam", "The user should be named Sam")
expect(t, !user.IsSuperAdmin, "Sam should not be a super admin") expect(t, !user.IsSuperAdmin, "Sam should not be a super admin")
@ -178,9 +167,9 @@ func userStoreTest(t *testing.T, newUserID int) {
expect(t, !user.IsBanned, "Sam should not be banned") expect(t, !user.IsBanned, "Sam should not be banned")
expectIntToBeX(t, user.Group, 5, "Sam should be in group 5") expectIntToBeX(t, user.Group, 5, "Sam should be in group 5")
if hasCache { if ucache != nil {
expectIntToBeX(t, ucache.Length(), 1, "User cache length should be 1, not %d") expectIntToBeX(t, ucache.Length(), 1, "User cache length should be 1, not %d")
user, err = ucache.CacheGet(newUserID) user, err = ucache.Get(newUserID)
recordMustExist(t, err, "Couldn't find UID #%d in the cache", newUserID) recordMustExist(t, err, "Couldn't find UID #%d in the cache", newUserID)
expect(t, user.ID == newUserID, fmt.Sprintf("user.ID does not match the requested UID. Got '%d' instead.", user.ID)) expect(t, user.ID == newUserID, fmt.Sprintf("user.ID does not match the requested UID. Got '%d' instead.", user.ID))
} }
@ -190,9 +179,9 @@ func userStoreTest(t *testing.T, newUserID int) {
expectIntToBeX(t, user.Group, 5, "Sam should still be in group 5 in this copy") expectIntToBeX(t, user.Group, 5, "Sam should still be in group 5 in this copy")
// ? - What if we change the caching mechanism so it isn't hard purged and reloaded? We'll deal with that when we come to it, but for now, this is a sign of a cache bug // ? - What if we change the caching mechanism so it isn't hard purged and reloaded? We'll deal with that when we come to it, but for now, this is a sign of a cache bug
if hasCache { if ucache != nil {
expectIntToBeX(t, ucache.Length(), 0, "User cache length should be 0, not %d") expectIntToBeX(t, ucache.Length(), 0, "User cache length should be 0, not %d")
_, err = ucache.CacheGet(newUserID) _, err = ucache.Get(newUserID)
recordMustNotExist(t, err, "UID #%d shouldn't be in the cache", newUserID) recordMustNotExist(t, err, "UID #%d shouldn't be in the cache", newUserID)
} }
@ -216,18 +205,16 @@ func userStoreTest(t *testing.T, newUserID int) {
expectNilErr(t, err) expectNilErr(t, err)
expect(t, user.Group == common.Config.DefaultGroup, fmt.Sprintf("Sam should be in group %d, not %d", common.Config.DefaultGroup, user.Group)) expect(t, user.Group == common.Config.DefaultGroup, fmt.Sprintf("Sam should be in group %d, not %d", common.Config.DefaultGroup, user.Group))
if hasCache { if ucache != nil {
expectIntToBeX(t, ucache.Length(), 0, "User cache length should be 0, not %d") expectIntToBeX(t, ucache.Length(), 0, "User cache length should be 0, not %d")
_, err = ucache.CacheGet(2) _, err = ucache.Get(2)
recordMustNotExist(t, err, "UID #%d shouldn't be in the cache", newUserID) recordMustNotExist(t, err, "UID #%d shouldn't be in the cache", newUserID)
} }
user, err = common.Users.Get(newUserID) user, err = common.Users.Get(newUserID)
recordMustExist(t, err, "Couldn't find UID #%d", newUserID) recordMustExist(t, err, "Couldn't find UID #%d", newUserID)
if user.ID != newUserID {
t.Errorf("The UID of the user record should be %d", newUserID)
}
expect(t, user.ID == newUserID, fmt.Sprintf("The UID of the user record should be %d", newUserID))
expect(t, !user.IsSuperAdmin, "Sam should not be a super admin") expect(t, !user.IsSuperAdmin, "Sam should not be a super admin")
expect(t, !user.IsAdmin, "Sam should not be an admin") expect(t, !user.IsAdmin, "Sam should not be an admin")
expect(t, !user.IsSuperMod, "Sam should not be a super mod") expect(t, !user.IsSuperMod, "Sam should not be a super mod")
@ -242,9 +229,9 @@ func userStoreTest(t *testing.T, newUserID int) {
expectNilErr(t, err) expectNilErr(t, err)
expectIntToBeX(t, user.Group, common.BanGroup, "Sam should still be in the ban group in this copy") expectIntToBeX(t, user.Group, common.BanGroup, "Sam should still be in the ban group in this copy")
if hasCache { if ucache != nil {
expectIntToBeX(t, ucache.Length(), 0, "User cache length should be 0, not %d") expectIntToBeX(t, ucache.Length(), 0, "User cache length should be 0, not %d")
_, err = ucache.CacheGet(newUserID) _, err = ucache.Get(newUserID)
recordMustNotExist(t, err, "UID #%d shouldn't be in the cache", newUserID) recordMustNotExist(t, err, "UID #%d shouldn't be in the cache", newUserID)
} }
@ -414,9 +401,9 @@ func userStoreTest(t *testing.T, newUserID int) {
expectNilErr(t, err) expectNilErr(t, err)
expect(t, !common.Users.Exists(newUserID), fmt.Sprintf("UID #%d should no longer exist", newUserID)) expect(t, !common.Users.Exists(newUserID), fmt.Sprintf("UID #%d should no longer exist", newUserID))
if hasCache { if ucache != nil {
expectIntToBeX(t, ucache.Length(), 0, "User cache length should be 0, not %d") expectIntToBeX(t, ucache.Length(), 0, "User cache length should be 0, not %d")
_, err = ucache.CacheGet(newUserID) _, err = ucache.Get(newUserID)
recordMustNotExist(t, err, "UID #%d shouldn't be in the cache", newUserID) recordMustNotExist(t, err, "UID #%d shouldn't be in the cache", newUserID)
} }
@ -505,10 +492,11 @@ func TestTopicStore(t *testing.T) {
} }
var err error var err error
common.Topics, err = common.NewMemoryTopicStore(common.Config.TopicCacheCapacity) tcache := common.NewMemoryTopicCache(common.Config.TopicCacheCapacity)
common.Topics, err = common.NewDefaultTopicStore(tcache)
expectNilErr(t, err) expectNilErr(t, err)
topicStoreTest(t) topicStoreTest(t)
common.Topics, err = common.NewSQLTopicStore() common.Topics, err = common.NewDefaultTopicStore(nil)
expectNilErr(t, err) expectNilErr(t, err)
topicStoreTest(t) topicStoreTest(t)
} }
@ -526,25 +514,19 @@ func topicStoreTest(t *testing.T) {
recordMustExist(t, err, "Couldn't find TID #1") recordMustExist(t, err, "Couldn't find TID #1")
if topic.ID != 1 { if topic.ID != 1 {
t.Error("topic.ID does not match the requested TID. Got '" + strconv.Itoa(topic.ID) + "' instead.") t.Error("topic.ID does not match the requested TID. Got '%d' instead.", topic.ID)
} }
// TODO: Add BulkGetMap() to the TopicStore // TODO: Add BulkGetMap() to the TopicStore
ok := common.Topics.Exists(-1) ok := common.Topics.Exists(-1)
if ok { expect(t, !ok, "TID #-1 shouldn't exist")
t.Error("TID #-1 shouldn't exist")
}
ok = common.Topics.Exists(0) ok = common.Topics.Exists(0)
if ok { expect(t, !ok, "TID #0 shouldn't exist")
t.Error("TID #0 shouldn't exist")
}
ok = common.Topics.Exists(1) ok = common.Topics.Exists(1)
if !ok { expect(t, ok, "TID #1 should exist")
t.Error("TID #1 should exist")
}
count := common.Topics.GlobalCount() count := common.Topics.GlobalCount()
if count <= 0 { if count <= 0 {
@ -563,17 +545,17 @@ func TestForumStore(t *testing.T) {
common.InitPlugins() common.InitPlugins()
} }
_, err := common.Fstore.Get(-1) _, err := common.Forums.Get(-1)
recordMustNotExist(t, err, "FID #-1 shouldn't exist") recordMustNotExist(t, err, "FID #-1 shouldn't exist")
_, err = common.Fstore.Get(0) _, err = common.Forums.Get(0)
recordMustNotExist(t, err, "FID #0 shouldn't exist") recordMustNotExist(t, err, "FID #0 shouldn't exist")
forum, err := common.Fstore.Get(1) forum, err := common.Forums.Get(1)
recordMustExist(t, err, "Couldn't find FID #1") recordMustExist(t, err, "Couldn't find FID #1")
if forum.ID != 1 { if forum.ID != 1 {
t.Error("forum.ID doesn't not match the requested FID. Got '" + strconv.Itoa(forum.ID) + "' instead.'") t.Error("forum.ID doesn't not match the requested FID. Got '%d' instead.'", forum.ID)
} }
// TODO: Check the preset and forum permissions // TODO: Check the preset and forum permissions
expect(t, forum.Name == "Reports", fmt.Sprintf("FID #0 is named '%s' and not 'Reports'", forum.Name)) expect(t, forum.Name == "Reports", fmt.Sprintf("FID #0 is named '%s' and not 'Reports'", forum.Name))
@ -581,7 +563,7 @@ func TestForumStore(t *testing.T) {
var expectDesc = "All the reports go here" var expectDesc = "All the reports go here"
expect(t, forum.Desc == expectDesc, fmt.Sprintf("The forum description should be '%s' not '%s'", expectDesc, forum.Desc)) expect(t, forum.Desc == expectDesc, fmt.Sprintf("The forum description should be '%s' not '%s'", expectDesc, forum.Desc))
forum, err = common.Fstore.Get(2) forum, err = common.Forums.Get(2)
recordMustExist(t, err, "Couldn't find FID #1") recordMustExist(t, err, "Couldn't find FID #1")
expect(t, forum.ID == 2, fmt.Sprintf("The FID should be 2 not %d", forum.ID)) expect(t, forum.ID == 2, fmt.Sprintf("The FID should be 2 not %d", forum.ID))
@ -590,11 +572,11 @@ func TestForumStore(t *testing.T) {
expectDesc = "A place for general discussions which don't fit elsewhere" expectDesc = "A place for general discussions which don't fit elsewhere"
expect(t, forum.Desc == expectDesc, fmt.Sprintf("The forum description should be '%s' not '%s'", expectDesc, forum.Desc)) expect(t, forum.Desc == expectDesc, fmt.Sprintf("The forum description should be '%s' not '%s'", expectDesc, forum.Desc))
ok := common.Fstore.Exists(-1) ok := common.Forums.Exists(-1)
expect(t, !ok, "FID #-1 shouldn't exist") expect(t, !ok, "FID #-1 shouldn't exist")
ok = common.Fstore.Exists(0) ok = common.Forums.Exists(0)
expect(t, !ok, "FID #0 shouldn't exist") expect(t, !ok, "FID #0 shouldn't exist")
ok = common.Fstore.Exists(1) ok = common.Forums.Exists(1)
expect(t, ok, "FID #1 should exist") expect(t, ok, "FID #1 should exist")
// TODO: Test forum creation // TODO: Test forum creation
@ -621,43 +603,38 @@ func TestGroupStore(t *testing.T) {
common.InitPlugins() common.InitPlugins()
} }
_, err := common.Gstore.Get(-1) _, err := common.Groups.Get(-1)
recordMustNotExist(t, err, "GID #-1 shouldn't exist") recordMustNotExist(t, err, "GID #-1 shouldn't exist")
// TODO: Refactor the group store to remove GID #0 // TODO: Refactor the group store to remove GID #0
group, err := common.Gstore.Get(0) group, err := common.Groups.Get(0)
recordMustExist(t, err, "Couldn't find GID #0") recordMustExist(t, err, "Couldn't find GID #0")
if group.ID != 0 { expect(t, group.ID == 0, fmt.Sprintf("group.ID doesn't not match the requested GID. Got '%d' instead.", group.ID))
t.Errorf("group.ID doesn't not match the requested GID. Got '%d' instead.", group.ID)
}
expect(t, group.Name == "Unknown", fmt.Sprintf("GID #0 is named '%s' and not 'Unknown'", group.Name)) expect(t, group.Name == "Unknown", fmt.Sprintf("GID #0 is named '%s' and not 'Unknown'", group.Name))
group, err = common.Gstore.Get(1) group, err = common.Groups.Get(1)
recordMustExist(t, err, "Couldn't find GID #1") recordMustExist(t, err, "Couldn't find GID #1")
expect(t, group.ID == 1, fmt.Sprintf("group.ID doesn't not match the requested GID. Got '%d' instead.'", group.ID))
if group.ID != 1 { ok := common.Groups.Exists(-1)
t.Errorf("group.ID doesn't not match the requested GID. Got '%d' instead.'", group.ID)
}
ok := common.Gstore.Exists(-1)
expect(t, !ok, "GID #-1 shouldn't exist") expect(t, !ok, "GID #-1 shouldn't exist")
// 0 aka Unknown, for system posts and other oddities // 0 aka Unknown, for system posts and other oddities
ok = common.Gstore.Exists(0) ok = common.Groups.Exists(0)
expect(t, ok, "GID #0 should exist") expect(t, ok, "GID #0 should exist")
ok = common.Gstore.Exists(1) ok = common.Groups.Exists(1)
expect(t, ok, "GID #1 should exist") expect(t, ok, "GID #1 should exist")
var isAdmin = true var isAdmin = true
var isMod = true var isMod = true
var isBanned = false var isBanned = false
gid, err := common.Gstore.Create("Testing", "Test", isAdmin, isMod, isBanned) gid, err := common.Groups.Create("Testing", "Test", isAdmin, isMod, isBanned)
expectNilErr(t, err) expectNilErr(t, err)
expect(t, common.Gstore.Exists(gid), "The group we just made doesn't exist") expect(t, common.Groups.Exists(gid), "The group we just made doesn't exist")
group, err = common.Gstore.Get(gid) group, err = common.Groups.Get(gid)
expectNilErr(t, err) expectNilErr(t, err)
expect(t, group.ID == gid, "The group ID should match the requested ID") expect(t, group.ID == gid, "The group ID should match the requested ID")
expect(t, group.IsAdmin, "This should be an admin group") expect(t, group.IsAdmin, "This should be an admin group")
@ -667,11 +644,11 @@ func TestGroupStore(t *testing.T) {
isAdmin = false isAdmin = false
isMod = true isMod = true
isBanned = true isBanned = true
gid, err = common.Gstore.Create("Testing 2", "Test", isAdmin, isMod, isBanned) gid, err = common.Groups.Create("Testing 2", "Test", isAdmin, isMod, isBanned)
expectNilErr(t, err) expectNilErr(t, err)
expect(t, common.Gstore.Exists(gid), "The group we just made doesn't exist") expect(t, common.Groups.Exists(gid), "The group we just made doesn't exist")
group, err = common.Gstore.Get(gid) group, err = common.Groups.Get(gid)
expectNilErr(t, err) expectNilErr(t, err)
expect(t, group.ID == gid, "The group ID should match the requested ID") expect(t, group.ID == gid, "The group ID should match the requested ID")
expect(t, !group.IsAdmin, "This should not be an admin group") expect(t, !group.IsAdmin, "This should not be an admin group")
@ -682,7 +659,7 @@ func TestGroupStore(t *testing.T) {
err = group.ChangeRank(false, false, true) err = group.ChangeRank(false, false, true)
expectNilErr(t, err) expectNilErr(t, err)
group, err = common.Gstore.Get(gid) group, err = common.Groups.Get(gid)
expectNilErr(t, err) expectNilErr(t, err)
expect(t, group.ID == gid, "The group ID should match the requested ID") expect(t, group.ID == gid, "The group ID should match the requested ID")
expect(t, !group.IsAdmin, "This shouldn't be an admin group") expect(t, !group.IsAdmin, "This shouldn't be an admin group")
@ -692,7 +669,7 @@ func TestGroupStore(t *testing.T) {
err = group.ChangeRank(true, true, true) err = group.ChangeRank(true, true, true)
expectNilErr(t, err) expectNilErr(t, err)
group, err = common.Gstore.Get(gid) group, err = common.Groups.Get(gid)
expectNilErr(t, err) expectNilErr(t, err)
expect(t, group.ID == gid, "The group ID should match the requested ID") expect(t, group.ID == gid, "The group ID should match the requested ID")
expect(t, group.IsAdmin, "This should be an admin group") expect(t, group.IsAdmin, "This should be an admin group")
@ -702,7 +679,7 @@ func TestGroupStore(t *testing.T) {
err = group.ChangeRank(false, true, true) err = group.ChangeRank(false, true, true)
expectNilErr(t, err) expectNilErr(t, err)
group, err = common.Gstore.Get(gid) group, err = common.Groups.Get(gid)
expectNilErr(t, err) expectNilErr(t, err)
expect(t, group.ID == gid, "The group ID should match the requested ID") expect(t, group.ID == gid, "The group ID should match the requested ID")
expect(t, !group.IsAdmin, "This shouldn't be an admin group") expect(t, !group.IsAdmin, "This shouldn't be an admin group")
@ -710,9 +687,9 @@ func TestGroupStore(t *testing.T) {
expect(t, !group.IsBanned, "This shouldn't be a ban group") expect(t, !group.IsBanned, "This shouldn't be a ban group")
// Make sure the data is static // Make sure the data is static
common.Gstore.Reload(gid) common.Groups.Reload(gid)
group, err = common.Gstore.Get(gid) group, err = common.Groups.Get(gid)
expectNilErr(t, err) expectNilErr(t, err)
expect(t, group.ID == gid, "The group ID should match the requested ID") expect(t, group.ID == gid, "The group ID should match the requested ID")
expect(t, !group.IsAdmin, "This shouldn't be an admin group") expect(t, !group.IsAdmin, "This shouldn't be an admin group")

View File

@ -15,6 +15,7 @@ import (
// TODO: Update the stats after edits so that we don't under or over decrement stats during deletes // TODO: Update the stats after edits so that we don't under or over decrement stats during deletes
// TODO: Disable stat updates in posts handled by plugin_guilds // TODO: Disable stat updates in posts handled by plugin_guilds
// TODO: Make sure this route is member only
func routeEditTopic(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routeEditTopic(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
@ -50,7 +51,7 @@ func routeEditTopic(w http.ResponseWriter, r *http.Request, user common.User) co
return common.InternalErrorJSQ(err, w, r, isJs) return common.InternalErrorJSQ(err, w, r, isJs)
} }
err = common.Fstore.UpdateLastTopic(topic.ID, user.ID, topic.ParentID) err = common.Forums.UpdateLastTopic(topic.ID, user.ID, topic.ParentID)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
return common.InternalErrorJSQ(err, w, r, isJs) return common.InternalErrorJSQ(err, w, r, isJs)
} }
@ -65,11 +66,12 @@ func routeEditTopic(w http.ResponseWriter, r *http.Request, user common.User) co
// TODO: Add support for soft-deletion and add a permission for hard delete in addition to the usual // TODO: Add support for soft-deletion and add a permission for hard delete in addition to the usual
// TODO: Disable stat updates in posts handled by plugin_guilds // TODO: Disable stat updates in posts handled by plugin_guilds
// TODO: Make sure this route is member only
func routeDeleteTopic(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routeDeleteTopic(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
// TODO: Move this to some sort of middleware // TODO: Move this to some sort of middleware
var tids []int var tids []int
var isJs = false var isJs = false
if r.Header.Get("Content-type") == "application/json" { if common.ReqIsJson(r) {
if r.Body == nil { if r.Body == nil {
return common.PreErrorJS("No request body", w, r) return common.PreErrorJS("No request body", w, r)
} }
@ -114,7 +116,7 @@ func routeDeleteTopic(w http.ResponseWriter, r *http.Request, user common.User)
return common.InternalErrorJSQ(err, w, r, isJs) return common.InternalErrorJSQ(err, w, r, isJs)
} }
err = common.AddModLog("delete", tid, "topic", user.LastIP, user.ID) err = common.ModLogs.Create("delete", tid, "topic", user.LastIP, user.ID)
if err != nil { if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs) return common.InternalErrorJSQ(err, w, r, isJs)
} }
@ -158,7 +160,7 @@ func routeStickTopic(w http.ResponseWriter, r *http.Request, user common.User) c
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
err = common.AddModLog("stick", tid, "topic", user.LastIP, user.ID) err = common.ModLogs.Create("stick", tid, "topic", user.LastIP, user.ID)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -197,7 +199,7 @@ func routeUnstickTopic(w http.ResponseWriter, r *http.Request, user common.User)
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
err = common.AddModLog("unstick", tid, "topic", user.LastIP, user.ID) err = common.ModLogs.Create("unstick", tid, "topic", user.LastIP, user.ID)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -214,7 +216,7 @@ func routeLockTopic(w http.ResponseWriter, r *http.Request, user common.User) co
// TODO: Move this to some sort of middleware // TODO: Move this to some sort of middleware
var tids []int var tids []int
var isJs = false var isJs = false
if r.Header.Get("Content-type") == "application/json" { if common.ReqIsJson(r) {
if r.Body == nil { if r.Body == nil {
return common.PreErrorJS("No request body", w, r) return common.PreErrorJS("No request body", w, r)
} }
@ -256,7 +258,7 @@ func routeLockTopic(w http.ResponseWriter, r *http.Request, user common.User) co
return common.InternalErrorJSQ(err, w, r, isJs) return common.InternalErrorJSQ(err, w, r, isJs)
} }
err = common.AddModLog("lock", tid, "topic", user.LastIP, user.ID) err = common.ModLogs.Create("lock", tid, "topic", user.LastIP, user.ID)
if err != nil { if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs) return common.InternalErrorJSQ(err, w, r, isJs)
} }
@ -299,7 +301,7 @@ func routeUnlockTopic(w http.ResponseWriter, r *http.Request, user common.User)
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
err = common.AddModLog("unlock", tid, "topic", user.LastIP, user.ID) err = common.ModLogs.Create("unlock", tid, "topic", user.LastIP, user.ID)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -425,7 +427,7 @@ func routeReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user common.
return common.InternalErrorJSQ(err, w, r, isJs) return common.InternalErrorJSQ(err, w, r, isJs)
} }
err = common.AddModLog("delete", reply.ParentID, "reply", user.LastIP, user.ID) err = common.ModLogs.Create("delete", reply.ParentID, "reply", user.LastIP, user.ID)
if err != nil { if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs) return common.InternalErrorJSQ(err, w, r, isJs)
} }
@ -588,7 +590,7 @@ func routeIps(w http.ResponseWriter, r *http.Request, user common.User) common.R
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
pi := common.IPSearchPage{"IP Search", user, headerVars, userList, ip} pi := common.IPSearchPage{common.GetTitlePhrase("ip-search"), user, headerVars, userList, ip}
if common.PreRenderHooks["pre_render_ips"] != nil { if common.PreRenderHooks["pre_render_ips"] != nil {
if common.RunPreRenderHook("pre_render_ips", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_ips", w, r, &user, &pi) {
return nil return nil
@ -610,9 +612,9 @@ func routeBanSubmit(w http.ResponseWriter, r *http.Request, user common.User) co
if err != nil { if err != nil {
return common.LocalError("The provided common.User ID is not a valid number.", w, r, user) return common.LocalError("The provided common.User ID is not a valid number.", w, r, user)
} }
/*if uid == -2 { if uid == -2 {
return common.LocalError("Stop trying to ban Merlin! Ban admin! Bad! No!",w,r,user) return common.LocalError("Why don't you like Merlin?", w, r, user)
}*/ }
targetUser, err := common.Users.Get(uid) targetUser, err := common.Users.Get(uid)
if err == ErrNoRows { if err == ErrNoRows {
@ -622,7 +624,7 @@ func routeBanSubmit(w http.ResponseWriter, r *http.Request, user common.User) co
} }
// TODO: Is there a difference between IsMod and IsSuperMod? Should we delete the redundant one? // TODO: Is there a difference between IsMod and IsSuperMod? Should we delete the redundant one?
if targetUser.IsSuperAdmin || targetUser.IsAdmin || targetUser.IsMod { if targetUser.IsMod {
return common.LocalError("You may not ban another staff member.", w, r, user) return common.LocalError("You may not ban another staff member.", w, r, user)
} }
if uid == user.ID { if uid == user.ID {
@ -665,7 +667,7 @@ func routeBanSubmit(w http.ResponseWriter, r *http.Request, user common.User) co
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
err = common.AddModLog("ban", uid, "user", user.LastIP, user.ID) err = common.ModLogs.Create("ban", uid, "user", user.LastIP, user.ID)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -704,7 +706,7 @@ func routeUnban(w http.ResponseWriter, r *http.Request, user common.User) common
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
err = common.AddModLog("unban", uid, "user", user.LastIP, user.ID) err = common.ModLogs.Create("unban", uid, "user", user.LastIP, user.ID)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -738,7 +740,7 @@ func routeActivate(w http.ResponseWriter, r *http.Request, user common.User) com
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
err = common.AddModLog("activate", targetUser.ID, "user", user.LastIP, user.ID) err = common.ModLogs.Create("activate", targetUser.ID, "user", user.LastIP, user.ID)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }

View File

@ -28,6 +28,16 @@ func routePanel(w http.ResponseWriter, r *http.Request, user common.User) common
var cpustr = "Unknown" var cpustr = "Unknown"
var cpuColour string var cpuColour string
lessThanSwitch := func(number int, lowerBound int, midBound int) string {
switch {
case number < lowerBound:
return "stat_green"
case number < midBound:
return "stat_orange"
}
return "stat_red"
}
var ramstr, ramColour string var ramstr, ramColour string
memres, err := mem.VirtualMemory() memres, err := mem.VirtualMemory()
if err != nil { if err != nil {
@ -37,7 +47,6 @@ func routePanel(w http.ResponseWriter, r *http.Request, user common.User) common
usedCount := common.ConvertByteInUnit(float64(memres.Total-memres.Available), totalUnit) usedCount := common.ConvertByteInUnit(float64(memres.Total-memres.Available), totalUnit)
// Round totals with .9s up, it's how most people see it anyway. Floats are notoriously imprecise, so do it off 0.85 // Round totals with .9s up, it's how most people see it anyway. Floats are notoriously imprecise, so do it off 0.85
//log.Print("pre used_count",used_count)
var totstr string var totstr string
if (totalCount - float64(int(totalCount))) > 0.85 { if (totalCount - float64(int(totalCount))) > 0.85 {
usedCount += 1.0 - (totalCount - float64(int(totalCount))) usedCount += 1.0 - (totalCount - float64(int(totalCount)))
@ -45,7 +54,6 @@ func routePanel(w http.ResponseWriter, r *http.Request, user common.User) common
} else { } else {
totstr = fmt.Sprintf("%.1f", totalCount) totstr = fmt.Sprintf("%.1f", totalCount)
} }
//log.Print("post used_count",used_count)
if usedCount > totalCount { if usedCount > totalCount {
usedCount = totalCount usedCount = totalCount
@ -53,31 +61,27 @@ func routePanel(w http.ResponseWriter, r *http.Request, user common.User) common
ramstr = fmt.Sprintf("%.1f", usedCount) + " / " + totstr + totalUnit ramstr = fmt.Sprintf("%.1f", usedCount) + " / " + totstr + totalUnit
ramperc := ((memres.Total - memres.Available) * 100) / memres.Total ramperc := ((memres.Total - memres.Available) * 100) / memres.Total
//log.Print("ramperc",ramperc) ramColour = lessThanSwitch(int(ramperc), 50, 75)
if ramperc < 50 {
ramColour = "stat_green"
} else if ramperc < 75 {
ramColour = "stat_orange"
} else {
ramColour = "stat_red"
}
} }
greaterThanSwitch := func(number int, lowerBound int, midBound int) string {
switch {
case number > midBound:
return "stat_green"
case number > lowerBound:
return "stat_orange"
}
return "stat_red"
}
// TODO: Add a stat store for this?
var postCount int var postCount int
err = stmts.todaysPostCount.QueryRow().Scan(&postCount) err = stmts.todaysPostCount.QueryRow().Scan(&postCount)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
var postInterval = "day" var postInterval = "day"
var postColour = greaterThanSwitch(postCount, 5, 25)
var postColour string
if postCount > 25 {
postColour = "stat_green"
} else if postCount > 5 {
postColour = "stat_orange"
} else {
postColour = "stat_red"
}
var topicCount int var topicCount int
err = stmts.todaysTopicCount.QueryRow().Scan(&topicCount) err = stmts.todaysTopicCount.QueryRow().Scan(&topicCount)
@ -85,15 +89,7 @@ func routePanel(w http.ResponseWriter, r *http.Request, user common.User) common
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
var topicInterval = "day" var topicInterval = "day"
var topicColour = greaterThanSwitch(topicCount, 0, 8)
var topicColour string
if topicCount > 8 {
topicColour = "stat_green"
} else if topicCount > 0 {
topicColour = "stat_orange"
} else {
topicColour = "stat_red"
}
var reportCount int var reportCount int
err = stmts.todaysReportCount.QueryRow().Scan(&reportCount) err = stmts.todaysReportCount.QueryRow().Scan(&reportCount)
@ -120,32 +116,9 @@ func routePanel(w http.ResponseWriter, r *http.Request, user common.User) common
gonline := wsHub.guestCount() gonline := wsHub.guestCount()
totonline := uonline + gonline totonline := uonline + gonline
var onlineColour string var onlineColour = greaterThanSwitch(totonline, 3, 10)
if totonline > 10 { var onlineGuestsColour = greaterThanSwitch(gonline, 1, 10)
onlineColour = "stat_green" var onlineUsersColour = greaterThanSwitch(uonline, 1, 5)
} else if totonline > 3 {
onlineColour = "stat_orange"
} else {
onlineColour = "stat_red"
}
var onlineGuestsColour string
if gonline > 10 {
onlineGuestsColour = "stat_green"
} else if gonline > 1 {
onlineGuestsColour = "stat_orange"
} else {
onlineGuestsColour = "stat_red"
}
var onlineUsersColour string
if uonline > 5 {
onlineUsersColour = "stat_green"
} else if uonline > 1 {
onlineUsersColour = "stat_orange"
} else {
onlineUsersColour = "stat_red"
}
totonline, totunit := common.ConvertFriendlyUnit(totonline) totonline, totunit := common.ConvertFriendlyUnit(totonline)
uonline, uunit := common.ConvertFriendlyUnit(uonline) uonline, uunit := common.ConvertFriendlyUnit(uonline)
@ -168,7 +141,7 @@ func routePanel(w http.ResponseWriter, r *http.Request, user common.User) common
gridElements = append(gridElements, common.GridElement{"dash-visitorsperweek", "2 visitors / week", 13, "grid_stat stat_disabled", "", "", "Coming Soon!" /*"The number of unique visitors we've had over the last 7 days"*/}) gridElements = append(gridElements, common.GridElement{"dash-visitorsperweek", "2 visitors / week", 13, "grid_stat stat_disabled", "", "", "Coming Soon!" /*"The number of unique visitors we've had over the last 7 days"*/})
gridElements = append(gridElements, common.GridElement{"dash-postsperuser", "5 posts / user / week", 14, "grid_stat stat_disabled", "", "", "Coming Soon!" /*"The average number of posts made by each active user over the past week"*/}) gridElements = append(gridElements, common.GridElement{"dash-postsperuser", "5 posts / user / week", 14, "grid_stat stat_disabled", "", "", "Coming Soon!" /*"The average number of posts made by each active user over the past week"*/})
pi := common.PanelDashboardPage{"Control Panel Dashboard", user, headerVars, stats, gridElements} pi := common.PanelDashboardPage{common.GetTitlePhrase("panel-dashboard"), user, headerVars, stats, gridElements}
if common.PreRenderHooks["pre_render_panel_dashboard"] != nil { if common.PreRenderHooks["pre_render_panel_dashboard"] != nil {
if common.RunPreRenderHook("pre_render_panel_dashboard", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_dashboard", w, r, &user, &pi) {
return nil return nil
@ -192,7 +165,7 @@ func routePanelForums(w http.ResponseWriter, r *http.Request, user common.User)
// TODO: Paginate this? // TODO: Paginate this?
var forumList []interface{} var forumList []interface{}
forums, err := common.Fstore.GetAll() forums, err := common.Forums.GetAll()
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -207,7 +180,7 @@ func routePanelForums(w http.ResponseWriter, r *http.Request, user common.User)
forumList = append(forumList, fadmin) forumList = append(forumList, fadmin)
} }
} }
pi := common.PanelPage{"Forum Manager", user, headerVars, stats, forumList, nil} pi := common.PanelPage{common.GetTitlePhrase("panel-forums"), user, headerVars, stats, forumList, nil}
if common.PreRenderHooks["pre_render_panel_forums"] != nil { if common.PreRenderHooks["pre_render_panel_forums"] != nil {
if common.RunPreRenderHook("pre_render_panel_forums", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_forums", w, r, &user, &pi) {
return nil return nil
@ -217,6 +190,7 @@ func routePanelForums(w http.ResponseWriter, r *http.Request, user common.User)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
return nil return nil
} }
@ -235,7 +209,7 @@ func routePanelForumsCreateSubmit(w http.ResponseWriter, r *http.Request, user c
factive := r.PostFormValue("forum-name") factive := r.PostFormValue("forum-name")
active := (factive == "on" || factive == "1") active := (factive == "on" || factive == "1")
_, err := common.Fstore.Create(fname, fdesc, active, fpreset) _, err := common.Forums.Create(fname, fdesc, active, fpreset)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -259,17 +233,18 @@ func routePanelForumsDelete(w http.ResponseWriter, r *http.Request, user common.
return common.LocalError("The provided Forum ID is not a valid number.", w, r, user) return common.LocalError("The provided Forum ID is not a valid number.", w, r, user)
} }
forum, err := common.Fstore.Get(fid) forum, err := common.Forums.Get(fid)
if err == ErrNoRows { if err == ErrNoRows {
return common.LocalError("The forum you're trying to delete doesn't exist.", w, r, user) return common.LocalError("The forum you're trying to delete doesn't exist.", w, r, user)
} else if err != nil { } else if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
// TODO: Make this a phrase
confirmMsg := "Are you sure you want to delete the '" + forum.Name + "' forum?" confirmMsg := "Are you sure you want to delete the '" + forum.Name + "' forum?"
yousure := common.AreYouSure{"/panel/forums/delete/submit/" + strconv.Itoa(fid), confirmMsg} yousure := common.AreYouSure{"/panel/forums/delete/submit/" + strconv.Itoa(fid), confirmMsg}
pi := common.PanelPage{"Delete Forum", user, headerVars, stats, tList, yousure} pi := common.PanelPage{common.GetTitlePhrase("panel-delete-forum"), user, headerVars, stats, tList, yousure}
if common.PreRenderHooks["pre_render_panel_delete_forum"] != nil { if common.PreRenderHooks["pre_render_panel_delete_forum"] != nil {
if common.RunPreRenderHook("pre_render_panel_delete_forum", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_delete_forum", w, r, &user, &pi) {
return nil return nil
@ -296,7 +271,7 @@ func routePanelForumsDeleteSubmit(w http.ResponseWriter, r *http.Request, user c
return common.LocalError("The provided Forum ID is not a valid number.", w, r, user) return common.LocalError("The provided Forum ID is not a valid number.", w, r, user)
} }
err = common.Fstore.Delete(fid) err = common.Forums.Delete(fid)
if err == ErrNoRows { if err == ErrNoRows {
return common.LocalError("The forum you're trying to delete doesn't exist.", w, r, user) return common.LocalError("The forum you're trying to delete doesn't exist.", w, r, user)
} else if err != nil { } else if err != nil {
@ -321,7 +296,7 @@ func routePanelForumsEdit(w http.ResponseWriter, r *http.Request, user common.Us
return common.LocalError("The provided Forum ID is not a valid number.", w, r, user) return common.LocalError("The provided Forum ID is not a valid number.", w, r, user)
} }
forum, err := common.Fstore.Get(fid) forum, err := common.Forums.Get(fid)
if err == ErrNoRows { if err == ErrNoRows {
return common.LocalError("The forum you're trying to edit doesn't exist.", w, r, user) return common.LocalError("The forum you're trying to edit doesn't exist.", w, r, user)
} else if err != nil { } else if err != nil {
@ -332,7 +307,7 @@ func routePanelForumsEdit(w http.ResponseWriter, r *http.Request, user common.Us
forum.Preset = "custom" forum.Preset = "custom"
} }
glist, err := common.Gstore.GetAll() glist, err := common.Groups.GetAll()
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -345,7 +320,7 @@ func routePanelForumsEdit(w http.ResponseWriter, r *http.Request, user common.Us
gplist = append(gplist, common.GroupForumPermPreset{group, common.ForumPermsToGroupForumPreset(group.Forums[fid])}) gplist = append(gplist, common.GroupForumPermPreset{group, common.ForumPermsToGroupForumPreset(group.Forums[fid])})
} }
pi := common.PanelEditForumPage{"Forum Editor", user, headerVars, stats, forum.ID, forum.Name, forum.Desc, forum.Active, forum.Preset, gplist} pi := common.PanelEditForumPage{common.GetTitlePhrase("panel-edit-forum"), user, headerVars, stats, forum.ID, forum.Name, forum.Desc, forum.Active, forum.Preset, gplist}
if common.PreRenderHooks["pre_render_panel_edit_forum"] != nil { if common.PreRenderHooks["pre_render_panel_edit_forum"] != nil {
if common.RunPreRenderHook("pre_render_panel_edit_forum", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_edit_forum", w, r, &user, &pi) {
return nil return nil
@ -355,6 +330,7 @@ func routePanelForumsEdit(w http.ResponseWriter, r *http.Request, user common.Us
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
return nil return nil
} }
@ -373,7 +349,7 @@ func routePanelForumsEditSubmit(w http.ResponseWriter, r *http.Request, user com
return common.LocalErrorJSQ("The provided Forum ID is not a valid number.", w, r, user, isJs) return common.LocalErrorJSQ("The provided Forum ID is not a valid number.", w, r, user, isJs)
} }
forum, err := common.Fstore.Get(fid) forum, err := common.Forums.Get(fid)
if err == ErrNoRows { if err == ErrNoRows {
return common.LocalErrorJSQ("The forum you're trying to edit doesn't exist.", w, r, user, isJs) return common.LocalErrorJSQ("The forum you're trying to edit doesn't exist.", w, r, user, isJs)
} else if err != nil { } else if err != nil {
@ -425,7 +401,7 @@ func routePanelForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, use
return common.LocalErrorJSQ("Invalid Group ID", w, r, user, isJs) return common.LocalErrorJSQ("Invalid Group ID", w, r, user, isJs)
} }
forum, err := common.Fstore.Get(fid) forum, err := common.Forums.Get(fid)
if err == ErrNoRows { if err == ErrNoRows {
return common.LocalErrorJSQ("This forum doesn't exist", w, r, user, isJs) return common.LocalErrorJSQ("This forum doesn't exist", w, r, user, isJs)
} else if err != nil { } else if err != nil {
@ -454,46 +430,35 @@ func routePanelSettings(w http.ResponseWriter, r *http.Request, user common.User
if !user.Perms.EditSettings { if !user.Perms.EditSettings {
return common.NoPermissions(w, r, user) return common.NoPermissions(w, r, user)
} }
var settingList = make(map[string]interface{}) var settingList = make(map[string]interface{})
rows, err := stmts.getSettings.Query()
settings, err := headerVars.Settings.BypassGetAll()
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
defer rows.Close()
// nolint need the type so people viewing this file understand what it returns without visiting setting.go // nolint need the type so people viewing this file understand what it returns without visiting phrases.go
var settingLabels map[string]string = common.GetAllSettingLabels() var settingLabels map[string]string = common.GetAllSettingLabels()
var sname, scontent, stype string for _, setting := range settings {
for rows.Next() { if setting.Type == "list" {
err := rows.Scan(&sname, &scontent, &stype) llist := settingLabels[setting.Name]
if err != nil {
return common.InternalError(err, w, r)
}
if stype == "list" {
llist := settingLabels[sname]
labels := strings.Split(llist, ",") labels := strings.Split(llist, ",")
conv, err := strconv.Atoi(scontent) conv, err := strconv.Atoi(setting.Content)
if err != nil { if err != nil {
return common.LocalError("The setting '"+sname+"' can't be converted to an integer", w, r, user) return common.LocalError("The setting '"+setting.Name+"' can't be converted to an integer", w, r, user)
} }
scontent = labels[conv-1] setting.Content = labels[conv-1]
} else if stype == "bool" { } else if setting.Type == "bool" {
if scontent == "1" { if setting.Content == "1" {
scontent = "Yes" setting.Content = "Yes"
} else { } else {
scontent = "No" setting.Content = "No"
} }
} }
settingList[sname] = scontent settingList[setting.Name] = setting.Content
}
err = rows.Err()
if err != nil {
return common.InternalError(err, w, r)
} }
pi := common.PanelPage{"Setting Manager", user, headerVars, stats, tList, settingList} pi := common.PanelPage{common.GetTitlePhrase("panel-settings"), user, headerVars, stats, tList, settingList}
if common.PreRenderHooks["pre_render_panel_settings"] != nil { if common.PreRenderHooks["pre_render_panel_settings"] != nil {
if common.RunPreRenderHook("pre_render_panel_settings", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_settings", w, r, &user, &pi) {
return nil return nil
@ -506,7 +471,7 @@ func routePanelSettings(w http.ResponseWriter, r *http.Request, user common.User
return nil return nil
} }
func routePanelSetting(w http.ResponseWriter, r *http.Request, user common.User, sname string) common.RouteError { func routePanelSettingEdit(w http.ResponseWriter, r *http.Request, user common.User, sname string) common.RouteError {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user) headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {
return ferr return ferr
@ -539,7 +504,7 @@ func routePanelSetting(w http.ResponseWriter, r *http.Request, user common.User,
} }
} }
pi := common.PanelPage{"Edit Setting", user, headerVars, stats, itemList, setting} pi := common.PanelPage{common.GetTitlePhrase("panel-edit-setting"), user, headerVars, stats, itemList, setting}
if common.PreRenderHooks["pre_render_panel_setting"] != nil { if common.PreRenderHooks["pre_render_panel_setting"] != nil {
if common.RunPreRenderHook("pre_render_panel_setting", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_setting", w, r, &user, &pi) {
return nil return nil
@ -552,7 +517,7 @@ func routePanelSetting(w http.ResponseWriter, r *http.Request, user common.User,
return nil return nil
} }
func routePanelSettingEdit(w http.ResponseWriter, r *http.Request, user common.User, sname string) common.RouteError { func routePanelSettingEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, sname string) common.RouteError {
headerLite, ferr := common.SimplePanelUserCheck(w, r, &user) headerLite, ferr := common.SimplePanelUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {
return ferr return ferr
@ -562,34 +527,14 @@ func routePanelSettingEdit(w http.ResponseWriter, r *http.Request, user common.U
} }
scontent := r.PostFormValue("setting-value") scontent := r.PostFormValue("setting-value")
setting, err := headerLite.Settings.BypassGet(sname) err := headerLite.Settings.Update(sname, scontent)
if err == ErrNoRows {
return common.LocalError("The setting you want to edit doesn't exist.", w, r, user)
} else if err != nil {
return common.InternalError(err, w, r)
}
if setting.Type == "bool" {
if scontent == "on" || scontent == "1" {
scontent = "1"
} else {
scontent = "0"
}
}
// TODO: Make this a method or function?
_, err = stmts.updateSetting.Exec(scontent, sname)
if err != nil { if err != nil {
if common.SafeSettingError(err) {
return common.LocalError(err.Error(), w, r, user)
}
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
errmsg := headerLite.Settings.ParseSetting(sname, scontent, setting.Type, setting.Constraint)
if errmsg != "" {
return common.LocalError(errmsg, w, r, user)
}
// TODO: Do a reload instead?
common.SettingBox.Store(headerLite.Settings)
http.Redirect(w, r, "/panel/settings/", http.StatusSeeOther) http.Redirect(w, r, "/panel/settings/", http.StatusSeeOther)
return nil return nil
} }
@ -604,7 +549,7 @@ func routePanelWordFilters(w http.ResponseWriter, r *http.Request, user common.U
} }
var filterList = common.WordFilterBox.Load().(common.WordFilterMap) var filterList = common.WordFilterBox.Load().(common.WordFilterMap)
pi := common.PanelPage{"Word Filter Manager", user, headerVars, stats, tList, filterList} pi := common.PanelPage{common.GetTitlePhrase("panel-word-filters"), user, headerVars, stats, tList, filterList}
if common.PreRenderHooks["pre_render_panel_word_filters"] != nil { if common.PreRenderHooks["pre_render_panel_word_filters"] != nil {
if common.RunPreRenderHook("pre_render_panel_word_filters", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_word_filters", w, r, &user, &pi) {
return nil return nil
@ -665,7 +610,7 @@ func routePanelWordFiltersEdit(w http.ResponseWriter, r *http.Request, user comm
_ = wfid _ = wfid
pi := common.PanelPage{"Edit Word Filter", user, headerVars, stats, tList, nil} pi := common.PanelPage{common.GetTitlePhrase("panel-edit-word-filter"), user, headerVars, stats, tList, nil}
if common.PreRenderHooks["pre_render_panel_word_filters_edit"] != nil { if common.PreRenderHooks["pre_render_panel_word_filters_edit"] != nil {
if common.RunPreRenderHook("pre_render_panel_word_filters_edit", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_word_filters_edit", w, r, &user, &pi) {
return nil return nil
@ -758,7 +703,7 @@ func routePanelPlugins(w http.ResponseWriter, r *http.Request, user common.User)
pluginList = append(pluginList, plugin) pluginList = append(pluginList, plugin)
} }
pi := common.PanelPage{"Plugin Manager", user, headerVars, stats, pluginList, nil} pi := common.PanelPage{common.GetTitlePhrase("panel-plugins"), user, headerVars, stats, pluginList, nil}
if common.PreRenderHooks["pre_render_panel_plugins"] != nil { if common.PreRenderHooks["pre_render_panel_plugins"] != nil {
if common.RunPreRenderHook("pre_render_panel_plugins", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_plugins", w, r, &user, &pi) {
return nil return nil
@ -966,8 +911,8 @@ func routePanelUsers(w http.ResponseWriter, r *http.Request, user common.User) c
puser.Avatar = strings.Replace(common.Config.Noavatar, "{id}", strconv.Itoa(puser.ID), 1) puser.Avatar = strings.Replace(common.Config.Noavatar, "{id}", strconv.Itoa(puser.ID), 1)
} }
if common.Gstore.DirtyGet(puser.Group).Tag != "" { if common.Groups.DirtyGet(puser.Group).Tag != "" {
puser.Tag = common.Gstore.DirtyGet(puser.Group).Tag puser.Tag = common.Groups.DirtyGet(puser.Group).Tag
} else { } else {
puser.Tag = "" puser.Tag = ""
} }
@ -979,7 +924,7 @@ func routePanelUsers(w http.ResponseWriter, r *http.Request, user common.User) c
} }
pageList := common.Paginate(stats.Users, perPage, 5) pageList := common.Paginate(stats.Users, perPage, 5)
pi := common.PanelUserPage{"User Manager", user, headerVars, stats, userList, pageList, page, lastPage} pi := common.PanelUserPage{common.GetTitlePhrase("panel-users"), user, headerVars, stats, userList, pageList, page, lastPage}
if common.PreRenderHooks["pre_render_panel_users"] != nil { if common.PreRenderHooks["pre_render_panel_users"] != nil {
if common.RunPreRenderHook("pre_render_panel_users", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_users", w, r, &user, &pi) {
return nil return nil
@ -1018,7 +963,7 @@ func routePanelUsersEdit(w http.ResponseWriter, r *http.Request, user common.Use
} }
// ? - Should we stop admins from deleting all the groups? Maybe, protect the group they're currently using? // ? - Should we stop admins from deleting all the groups? Maybe, protect the group they're currently using?
groups, err := common.Gstore.GetRange(1, 0) // ? - 0 = Go to the end groups, err := common.Groups.GetRange(1, 0) // ? - 0 = Go to the end
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -1034,7 +979,7 @@ func routePanelUsersEdit(w http.ResponseWriter, r *http.Request, user common.Use
groupList = append(groupList, group) groupList = append(groupList, group)
} }
pi := common.PanelPage{"User Editor", user, headerVars, stats, groupList, targetUser} pi := common.PanelPage{common.GetTitlePhrase("panel-edit-user"), user, headerVars, stats, groupList, targetUser}
if common.PreRenderHooks["pre_render_panel_edit_user"] != nil { if common.PreRenderHooks["pre_render_panel_edit_user"] != nil {
if common.RunPreRenderHook("pre_render_panel_edit_user", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_edit_user", w, r, &user, &pi) {
return nil return nil
@ -1095,7 +1040,7 @@ func routePanelUsersEditSubmit(w http.ResponseWriter, r *http.Request, user comm
return common.LocalError("You need to provide a whole number for the group ID", w, r, user) return common.LocalError("You need to provide a whole number for the group ID", w, r, user)
} }
group, err := common.Gstore.Get(newgroup) group, err := common.Groups.Get(newgroup)
if err == ErrNoRows { if err == ErrNoRows {
return common.LocalError("The group you're trying to place this user in doesn't exist.", w, r, user) return common.LocalError("The group you're trying to place this user in doesn't exist.", w, r, user)
} else if err != nil { } else if err != nil {
@ -1109,6 +1054,7 @@ func routePanelUsersEditSubmit(w http.ResponseWriter, r *http.Request, user comm
return common.LocalError("You need the EditUserGroupSuperMod permission to assign someone to a super mod group.", w, r, user) return common.LocalError("You need the EditUserGroupSuperMod permission to assign someone to a super mod group.", w, r, user)
} }
// TODO: Move this query into common
_, err = stmts.updateUser.Exec(newname, newemail, newgroup, targetUser.ID) _, err = stmts.updateUser.Exec(newname, newemail, newgroup, targetUser.ID)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
@ -1138,7 +1084,7 @@ func routePanelGroups(w http.ResponseWriter, r *http.Request, user common.User)
var count int var count int
var groupList []common.GroupAdmin var groupList []common.GroupAdmin
groups, _ := common.Gstore.GetRange(offset, 0) groups, _ := common.Groups.GetRange(offset, 0)
for _, group := range groups { for _, group := range groups {
if count == perPage { if count == perPage {
break break
@ -1149,6 +1095,7 @@ func routePanelGroups(w http.ResponseWriter, r *http.Request, user common.User)
var canEdit bool var canEdit bool
var canDelete = false var canDelete = false
// TODO: Use a switch for this
if group.IsAdmin { if group.IsAdmin {
rank = "Admin" rank = "Admin"
rankClass = "admin" rankClass = "admin"
@ -1173,7 +1120,7 @@ func routePanelGroups(w http.ResponseWriter, r *http.Request, user common.User)
//log.Printf("groupList: %+v\n", groupList) //log.Printf("groupList: %+v\n", groupList)
pageList := common.Paginate(stats.Groups, perPage, 5) pageList := common.Paginate(stats.Groups, perPage, 5)
pi := common.PanelGroupPage{"Group Manager", user, headerVars, stats, groupList, pageList, page, lastPage} pi := common.PanelGroupPage{common.GetTitlePhrase("panel-groups"), user, headerVars, stats, groupList, pageList, page, lastPage}
if common.PreRenderHooks["pre_render_panel_groups"] != nil { if common.PreRenderHooks["pre_render_panel_groups"] != nil {
if common.RunPreRenderHook("pre_render_panel_groups", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_groups", w, r, &user, &pi) {
return nil return nil
@ -1201,7 +1148,7 @@ func routePanelGroupsEdit(w http.ResponseWriter, r *http.Request, user common.Us
return common.LocalError("You need to provide a whole number for the group ID", w, r, user) return common.LocalError("You need to provide a whole number for the group ID", w, r, user)
} }
group, err := common.Gstore.Get(gid) group, err := common.Groups.Get(gid)
if err == ErrNoRows { if err == ErrNoRows {
//log.Print("aaaaa monsters") //log.Print("aaaaa monsters")
return common.NotFound(w, r) return common.NotFound(w, r)
@ -1232,7 +1179,7 @@ func routePanelGroupsEdit(w http.ResponseWriter, r *http.Request, user common.Us
disableRank := !user.Perms.EditGroupGlobalPerms || (group.ID == 6) disableRank := !user.Perms.EditGroupGlobalPerms || (group.ID == 6)
pi := common.PanelEditGroupPage{"Group Editor", user, headerVars, stats, group.ID, group.Name, group.Tag, rank, disableRank} pi := common.PanelEditGroupPage{common.GetTitlePhrase("panel-edit-group"), user, headerVars, stats, group.ID, group.Name, group.Tag, rank, disableRank}
if common.PreRenderHooks["pre_render_panel_edit_group"] != nil { if common.PreRenderHooks["pre_render_panel_edit_group"] != nil {
if common.RunPreRenderHook("pre_render_panel_edit_group", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_edit_group", w, r, &user, &pi) {
return nil return nil
@ -1259,7 +1206,7 @@ func routePanelGroupsEditPerms(w http.ResponseWriter, r *http.Request, user comm
return common.LocalError("The Group ID is not a valid integer.", w, r, user) return common.LocalError("The Group ID is not a valid integer.", w, r, user)
} }
group, err := common.Gstore.Get(gid) group, err := common.Groups.Get(gid)
if err == ErrNoRows { if err == ErrNoRows {
//log.Print("aaaaa monsters") //log.Print("aaaaa monsters")
return common.NotFound(w, r) return common.NotFound(w, r)
@ -1310,7 +1257,7 @@ func routePanelGroupsEditPerms(w http.ResponseWriter, r *http.Request, user comm
globalPerms = append(globalPerms, common.NameLangToggle{"ViewIPs", common.GetGlobalPermPhrase("ViewIPs"), group.Perms.ViewIPs}) globalPerms = append(globalPerms, common.NameLangToggle{"ViewIPs", common.GetGlobalPermPhrase("ViewIPs"), group.Perms.ViewIPs})
globalPerms = append(globalPerms, common.NameLangToggle{"UploadFiles", common.GetGlobalPermPhrase("UploadFiles"), group.Perms.UploadFiles}) globalPerms = append(globalPerms, common.NameLangToggle{"UploadFiles", common.GetGlobalPermPhrase("UploadFiles"), group.Perms.UploadFiles})
pi := common.PanelEditGroupPermsPage{"Group Editor", user, headerVars, stats, group.ID, group.Name, localPerms, globalPerms} pi := common.PanelEditGroupPermsPage{common.GetTitlePhrase("panel-edit-group"), user, headerVars, stats, group.ID, group.Name, localPerms, globalPerms}
if common.PreRenderHooks["pre_render_panel_edit_group_perms"] != nil { if common.PreRenderHooks["pre_render_panel_edit_group_perms"] != nil {
if common.RunPreRenderHook("pre_render_panel_edit_group_perms", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_edit_group_perms", w, r, &user, &pi) {
return nil return nil
@ -1337,7 +1284,7 @@ func routePanelGroupsEditSubmit(w http.ResponseWriter, r *http.Request, user com
return common.LocalError("You need to provide a whole number for the group ID", w, r, user) return common.LocalError("You need to provide a whole number for the group ID", w, r, user)
} }
group, err := common.Gstore.Get(gid) group, err := common.Groups.Get(gid)
if err == ErrNoRows { if err == ErrNoRows {
//log.Print("aaaaa monsters") //log.Print("aaaaa monsters")
return common.NotFound(w, r) return common.NotFound(w, r)
@ -1360,6 +1307,7 @@ func routePanelGroupsEditSubmit(w http.ResponseWriter, r *http.Request, user com
rank := r.FormValue("group-type") rank := r.FormValue("group-type")
var originalRank string var originalRank string
// TODO: Use a switch for this
if group.IsAdmin { if group.IsAdmin {
originalRank = "Admin" originalRank = "Admin"
} else if group.IsMod { } else if group.IsMod {
@ -1407,7 +1355,7 @@ func routePanelGroupsEditSubmit(w http.ResponseWriter, r *http.Request, user com
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
common.Gstore.Reload(gid) common.Groups.Reload(gid)
http.Redirect(w, r, "/panel/groups/edit/"+strconv.Itoa(gid), http.StatusSeeOther) http.Redirect(w, r, "/panel/groups/edit/"+strconv.Itoa(gid), http.StatusSeeOther)
return nil return nil
@ -1427,7 +1375,7 @@ func routePanelGroupsEditPermsSubmit(w http.ResponseWriter, r *http.Request, use
return common.LocalError("The Group ID is not a valid integer.", w, r, user) return common.LocalError("The Group ID is not a valid integer.", w, r, user)
} }
group, err := common.Gstore.Get(gid) group, err := common.Groups.Get(gid)
if err == ErrNoRows { if err == ErrNoRows {
//log.Print("aaaaa monsters o.o") //log.Print("aaaaa monsters o.o")
return common.NotFound(w, r) return common.NotFound(w, r)
@ -1508,7 +1456,7 @@ func routePanelGroupsCreateSubmit(w http.ResponseWriter, r *http.Request, user c
} }
} }
gid, err := common.Gstore.Create(groupName, groupTag, isAdmin, isMod, isBanned) gid, err := common.Groups.Create(groupName, groupTag, isAdmin, isMod, isBanned)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -1538,7 +1486,7 @@ func routePanelThemes(w http.ResponseWriter, r *http.Request, user common.User)
} }
pi := common.PanelThemesPage{"Theme Manager", user, headerVars, stats, pThemeList, vThemeList} pi := common.PanelThemesPage{common.GetTitlePhrase("panel-themes"), user, headerVars, stats, pThemeList, vThemeList}
if common.PreRenderHooks["pre_render_panel_themes"] != nil { if common.PreRenderHooks["pre_render_panel_themes"] != nil {
if common.RunPreRenderHook("pre_render_panel_themes", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_themes", w, r, &user, &pi) {
return nil return nil
@ -1658,7 +1606,7 @@ func routePanelBackups(w http.ResponseWriter, r *http.Request, user common.User,
backupList = append(backupList, common.BackupItem{backupFile.Name(), backupFile.ModTime()}) backupList = append(backupList, common.BackupItem{backupFile.Name(), backupFile.ModTime()})
} }
pi := common.PanelBackupPage{"Backups", user, headerVars, stats, backupList} pi := common.PanelBackupPage{common.GetTitlePhrase("panel-backups"), user, headerVars, stats, backupList}
err = common.Templates.ExecuteTemplate(w, "panel-backups.html", pi) err = common.Templates.ExecuteTemplate(w, "panel-backups.html", pi)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
@ -1672,12 +1620,7 @@ func routePanelLogsMod(w http.ResponseWriter, r *http.Request, user common.User)
return ferr return ferr
} }
var logCount int logCount := common.ModLogs.GlobalCount()
err := stmts.modlogCount.QueryRow().Scan(&logCount)
if err != nil {
return common.InternalError(err, w, r)
}
page, _ := strconv.Atoi(r.FormValue("page")) page, _ := strconv.Atoi(r.FormValue("page"))
perPage := 10 perPage := 10
offset, page, lastPage := common.PageOffset(logCount, page, perPage) offset, page, lastPage := common.PageOffset(logCount, page, perPage)
@ -1688,6 +1631,21 @@ func routePanelLogsMod(w http.ResponseWriter, r *http.Request, user common.User)
} }
defer rows.Close() defer rows.Close()
// TODO: Log errors when something really screwy is going on?
handleUnknownUser := func(user *common.User, err error) *common.User {
if err != nil {
return &common.User{Name: "Unknown", Link: common.BuildProfileURL("unknown", 0)}
}
return user
}
handleUnknownTopic := func(topic *common.Topic, err error) *common.Topic {
if err != nil {
return &common.Topic{Title: "Unknown", Link: common.BuildProfileURL("unknown", 0)}
}
return topic
}
var logs []common.LogItem var logs []common.LogItem
var action, elementType, ipaddress, doneAt string var action, elementType, ipaddress, doneAt string
var elementID, actorID int var elementID, actorID int
@ -1697,68 +1655,41 @@ func routePanelLogsMod(w http.ResponseWriter, r *http.Request, user common.User)
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
actor, err := common.Users.Get(actorID) actor := handleUnknownUser(common.Users.Get(actorID))
if err != nil {
actor = &common.User{Name: "Unknown", Link: common.BuildProfileURL("unknown", 0)}
}
switch action { switch action {
case "lock": case "lock":
topic, err := common.Topics.Get(elementID) topic := handleUnknownTopic(common.Topics.Get(elementID))
if err != nil { action = fmt.Sprintf("<a href='%s'>%s</a> was locked by <a href='%s'>%s</a>", topic.Link, topic.Title, actor.Link, actor.Name)
topic = &common.Topic{Title: "Unknown", Link: common.BuildProfileURL("unknown", 0)}
}
action = "<a href='" + topic.Link + "'>" + topic.Title + "</a> was locked by <a href='" + actor.Link + "'>" + actor.Name + "</a>"
case "unlock": case "unlock":
topic, err := common.Topics.Get(elementID) topic := handleUnknownTopic(common.Topics.Get(elementID))
if err != nil { action = fmt.Sprintf("<a href='%s'>%s</a> was reopened by <a href='%s'>%s</a>", topic.Link, topic.Title, actor.Link, actor.Name)
topic = &common.Topic{Title: "Unknown", Link: common.BuildProfileURL("unknown", 0)}
}
action = "<a href='" + topic.Link + "'>" + topic.Title + "</a> was reopened by <a href='" + actor.Link + "'>" + actor.Name + "</a>"
case "stick": case "stick":
topic, err := common.Topics.Get(elementID) topic := handleUnknownTopic(common.Topics.Get(elementID))
if err != nil { action = fmt.Sprintf("<a href='%s'>%s</a> was pinned by <a href='%s'>%s</a>", topic.Link, topic.Title, actor.Link, actor.Name)
topic = &common.Topic{Title: "Unknown", Link: common.BuildProfileURL("unknown", 0)}
}
action = "<a href='" + topic.Link + "'>" + topic.Title + "</a> was pinned by <a href='" + actor.Link + "'>" + actor.Name + "</a>"
case "unstick": case "unstick":
topic, err := common.Topics.Get(elementID) topic := handleUnknownTopic(common.Topics.Get(elementID))
if err != nil { action = fmt.Sprintf("<a href='%s'>%s</a> was unpinned by <a href='%s'>%s</a>", topic.Link, topic.Title, actor.Link, actor.Name)
topic = &common.Topic{Title: "Unknown", Link: common.BuildProfileURL("unknown", 0)}
}
action = "<a href='" + topic.Link + "'>" + topic.Title + "</a> was unpinned by <a href='" + actor.Link + "'>" + actor.Name + "</a>"
case "delete": case "delete":
if elementType == "topic" { if elementType == "topic" {
action = "Topic #" + strconv.Itoa(elementID) + " was deleted by <a href='" + actor.Link + "'>" + actor.Name + "</a>" action = fmt.Sprintf("Topic #%d was deleted by <a href='%s'>%s</a>", elementID, actor.Link, actor.Name)
} else { } else {
reply := common.BlankReply() reply := common.BlankReply()
reply.ID = elementID reply.ID = elementID
topic, err := reply.Topic() topic := handleUnknownTopic(reply.Topic())
if err != nil { action = fmt.Sprintf("A reply in <a href='%s'>%s</a> was deleted by <a href='%s'>%s</a>", topic.Link, topic.Title, actor.Link, actor.Name)
topic = &common.Topic{Title: "Unknown", Link: common.BuildProfileURL("unknown", 0)}
}
action = "A reply in <a href='" + topic.Link + "'>" + topic.Title + "</a> was deleted by <a href='" + actor.Link + "'>" + actor.Name + "</a>"
} }
case "ban": case "ban":
targetUser, err := common.Users.Get(elementID) targetUser := handleUnknownUser(common.Users.Get(elementID))
if err != nil { action = fmt.Sprintf("<a href='%s'>%s</a> was banned by <a href='%s'>%s</a>", targetUser.Link, targetUser.Name, actor.Link, actor.Name)
targetUser = &common.User{Name: "Unknown", Link: common.BuildProfileURL("unknown", 0)}
}
action = "<a href='" + targetUser.Link + "'>" + targetUser.Name + "</a> was banned by <a href='" + actor.Link + "'>" + actor.Name + "</a>"
case "unban": case "unban":
targetUser, err := common.Users.Get(elementID) targetUser := handleUnknownUser(common.Users.Get(elementID))
if err != nil { action = fmt.Sprintf("<a href='%s'>%s</a> was unbanned by <a href='%s'>%s</a>", targetUser.Link, targetUser.Name, actor.Link, actor.Name)
targetUser = &common.User{Name: "Unknown", Link: common.BuildProfileURL("unknown", 0)}
}
action = "<a href='" + targetUser.Link + "'>" + targetUser.Name + "</a> was unbanned by <a href='" + actor.Link + "'>" + actor.Name + "</a>"
case "activate": case "activate":
targetUser, err := common.Users.Get(elementID) targetUser := handleUnknownUser(common.Users.Get(elementID))
if err != nil { action = fmt.Sprintf("<a href='%s'>%s</a> was activated by <a href='%s'>%s</a>", targetUser.Link, targetUser.Name, actor.Link, actor.Name)
targetUser = &common.User{Name: "Unknown", Link: common.BuildProfileURL("unknown", 0)}
}
action = "<a href='" + targetUser.Link + "'>" + targetUser.Name + "</a> was activated by <a href='" + actor.Link + "'>" + actor.Name + "</a>"
default: default:
action = "Unknown action '" + action + "' by <a href='" + actor.Link + "'>" + actor.Name + "</a>" action = fmt.Sprintf("Unknown action '%s' by <a href='%s'>%s</a>", action, actor.Link, actor.Name)
} }
logs = append(logs, common.LogItem{Action: template.HTML(action), IPAddress: ipaddress, DoneAt: doneAt}) logs = append(logs, common.LogItem{Action: template.HTML(action), IPAddress: ipaddress, DoneAt: doneAt})
} }
@ -1768,7 +1699,7 @@ func routePanelLogsMod(w http.ResponseWriter, r *http.Request, user common.User)
} }
pageList := common.Paginate(logCount, perPage, 5) pageList := common.Paginate(logCount, perPage, 5)
pi := common.PanelLogsPage{"Moderation Logs", user, headerVars, stats, logs, pageList, page, lastPage} pi := common.PanelLogsPage{common.GetTitlePhrase("panel-mod-logs"), user, headerVars, stats, logs, pageList, page, lastPage}
if common.PreRenderHooks["pre_render_panel_mod_log"] != nil { if common.PreRenderHooks["pre_render_panel_mod_log"] != nil {
if common.RunPreRenderHook("pre_render_panel_mod_log", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_mod_log", w, r, &user, &pi) {
return nil return nil
@ -1792,7 +1723,7 @@ func routePanelDebug(w http.ResponseWriter, r *http.Request, user common.User) c
openConnCount := dbStats.OpenConnections openConnCount := dbStats.OpenConnections
// Disk I/O? // Disk I/O?
pi := common.PanelDebugPage{"Debug", user, headerVars, stats, uptime, openConnCount, dbAdapter} pi := common.PanelDebugPage{common.GetTitlePhrase("panel-debug"), user, headerVars, stats, uptime, openConnCount, dbAdapter}
err := common.Templates.ExecuteTemplate(w, "panel-debug.html", pi) err := common.Templates.ExecuteTemplate(w, "panel-debug.html", pi)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)

View File

@ -33,20 +33,21 @@ func initGuilds() (err error) {
router.HandleFunc("/guild/create/submit/", guilds.RouteCreateGuildSubmit) router.HandleFunc("/guild/create/submit/", guilds.RouteCreateGuildSubmit)
router.HandleFunc("/guild/members/", guilds.RouteMemberList) router.HandleFunc("/guild/members/", guilds.RouteMemberList)
guilds.Gstore, err = guilds.NewSQLGuildStore()
if err != nil {
return err
}
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
guilds.ListStmt = acc.Select("guilds").Columns("guildID, name, desc, active, privacy, joinable, owner, memberCount, createdAt, lastUpdateTime").Prepare() guilds.ListStmt = acc.Select("guilds").Columns("guildID, name, desc, active, privacy, joinable, owner, memberCount, createdAt, lastUpdateTime").Prepare()
guilds.GetGuildStmt = acc.Select("guilds").Columns("name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime").Where("guildID = ?").Prepare()
guilds.MemberListStmt = acc.Select("guilds_members").Columns("guildID, uid, rank, posts, joinedAt").Prepare() guilds.MemberListStmt = acc.Select("guilds_members").Columns("guildID, uid, rank, posts, joinedAt").Prepare()
guilds.MemberListJoinStmt = acc.SimpleLeftJoin("guilds_members", "users", "users.uid, guilds_members.rank, guilds_members.posts, guilds_members.joinedAt, users.name, users.avatar", "guilds_members.uid = users.uid", "guilds_members.guildID = ?", "guilds_members.rank DESC, guilds_members.joinedat ASC", "") guilds.MemberListJoinStmt = acc.SimpleLeftJoin("guilds_members", "users", "users.uid, guilds_members.rank, guilds_members.posts, guilds_members.joinedAt, users.name, users.avatar", "guilds_members.uid = users.uid", "guilds_members.guildID = ?", "guilds_members.rank DESC, guilds_members.joinedat ASC", "")
guilds.GetMemberStmt = acc.Select("guilds_members").Columns("rank, posts, joinedAt").Where("guildID = ? AND uid = ?").Prepare() guilds.GetMemberStmt = acc.Select("guilds_members").Columns("rank, posts, joinedAt").Where("guildID = ? AND uid = ?").Prepare()
guilds.CreateGuildStmt = acc.Insert("guilds").Columns("name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime").Fields("?,?,?,?,1,?,1,?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare()
guilds.AttachForumStmt = acc.Update("forums").Set("parentID = ?, parentType = 'guild'").Where("fid = ?").Prepare() guilds.AttachForumStmt = acc.Update("forums").Set("parentID = ?, parentType = 'guild'").Where("fid = ?").Prepare()
guilds.UnattachForumStmt = acc.Update("forums").Set("parentID = 0, parentType = ''").Where("fid = ?").Prepare() guilds.UnattachForumStmt = acc.Update("forums").Set("parentID = 0, parentType = ''").Where("fid = ?").Prepare()
@ -72,8 +73,6 @@ func deactivateGuilds() {
_ = guilds.MemberListStmt.Close() _ = guilds.MemberListStmt.Close()
_ = guilds.MemberListJoinStmt.Close() _ = guilds.MemberListJoinStmt.Close()
_ = guilds.GetMemberStmt.Close() _ = guilds.GetMemberStmt.Close()
_ = guilds.GetGuildStmt.Close()
_ = guilds.CreateGuildStmt.Close()
_ = guilds.AttachForumStmt.Close() _ = guilds.AttachForumStmt.Close()
_ = guilds.UnattachForumStmt.Close() _ = guilds.UnattachForumStmt.Close()
_ = guilds.AddMemberStmt.Close() _ = guilds.AddMemberStmt.Close()

View File

@ -1,4 +1,3 @@
/* WIP Under Construction */
package qgen package qgen
var Install *installer var Install *installer
@ -13,11 +12,21 @@ type DB_Install_Instruction struct {
Type string Type string
} }
// TODO: Add methods to this to construct it OO-like
type DB_Install_Table struct {
Name string
Charset string
Collation string
Columns []DBTableColumn
Keys []DBTableKey
}
// A set of wrappers around the generator methods, so we can use this in the installer // A set of wrappers around the generator methods, so we can use this in the installer
// TODO: Re-implement the query generation, query builder and installer adapters as layers on-top of a query text adapter // TODO: Re-implement the query generation, query builder and installer adapters as layers on-top of a query text adapter
type installer struct { type installer struct {
adapter Adapter adapter Adapter
instructions []DB_Install_Instruction instructions []DB_Install_Instruction
tables []*DB_Install_Table // TODO: Use this in Record() in the next commit to allow us to auto-migrate settings rather than manually patching them in on upgrade
plugins []QueryPlugin plugins []QueryPlugin
} }
@ -26,8 +35,7 @@ func (install *installer) SetAdapter(name string) error {
if err != nil { if err != nil {
return err return err
} }
install.adapter = adap install.SetAdapterInstance(adap)
install.instructions = []DB_Install_Instruction{}
return nil return nil
} }
@ -36,49 +44,54 @@ func (install *installer) SetAdapterInstance(adapter Adapter) {
install.instructions = []DB_Install_Instruction{} install.instructions = []DB_Install_Instruction{}
} }
func (install *installer) RegisterPlugin(plugin QueryPlugin) { func (install *installer) AddPlugins(plugins ...QueryPlugin) {
install.plugins = append(install.plugins, plugin) install.plugins = append(install.plugins, plugins...)
} }
func (install *installer) CreateTable(table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) error { func (install *installer) CreateTable(table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) error {
for _, plugin := range install.plugins { tableStruct := &DB_Install_Table{table, charset, collation, columns, keys}
err := plugin.Hook("CreateTableStart", table, charset, collation, columns, keys) err := install.RunHook("CreateTableStart", tableStruct)
if err != nil { if err != nil {
return err return err
} }
}
res, err := install.adapter.CreateTable("_installer", table, charset, collation, columns, keys) res, err := install.adapter.CreateTable("_installer", table, charset, collation, columns, keys)
if err != nil { if err != nil {
return err return err
} }
for _, plugin := range install.plugins { err = install.RunHook("CreateTableAfter", tableStruct)
err := plugin.Hook("CreateTableAfter", table, charset, collation, columns, keys, res)
if err != nil { if err != nil {
return err return err
} }
}
install.instructions = append(install.instructions, DB_Install_Instruction{table, res, "create-table"}) install.instructions = append(install.instructions, DB_Install_Instruction{table, res, "create-table"})
install.tables = append(install.tables, tableStruct)
return nil return nil
} }
// TODO: Let plugins manipulate the parameters like in CreateTable
func (install *installer) SimpleInsert(table string, columns string, fields string) error { func (install *installer) SimpleInsert(table string, columns string, fields string) error {
for _, plugin := range install.plugins { err := install.RunHook("SimpleInsertStart", table, columns, fields)
err := plugin.Hook("SimpleInsertStart", table, columns, fields)
if err != nil { if err != nil {
return err return err
} }
}
res, err := install.adapter.SimpleInsert("_installer", table, columns, fields) res, err := install.adapter.SimpleInsert("_installer", table, columns, fields)
if err != nil { if err != nil {
return err return err
} }
err = install.RunHook("SimpleInsertAfter", table, columns, fields, res)
if err != nil {
return err
}
install.instructions = append(install.instructions, DB_Install_Instruction{table, res, "insert"})
return nil
}
func (install *installer) RunHook(name string, args ...interface{}) error {
for _, plugin := range install.plugins { for _, plugin := range install.plugins {
err := plugin.Hook("SimpleInsertAfter", table, columns, fields, res) err := plugin.Hook(name, args...)
if err != nil { if err != nil {
return err return err
} }
} }
install.instructions = append(install.instructions, DB_Install_Instruction{table, res, "insert"})
return nil return nil
} }

View File

@ -26,7 +26,7 @@ func main() {
for _, adapter := range qgen.Registry { for _, adapter := range qgen.Registry {
log.Printf("Building the queries for the %s adapter", adapter.GetName()) log.Printf("Building the queries for the %s adapter", adapter.GetName())
qgen.Install.SetAdapterInstance(adapter) qgen.Install.SetAdapterInstance(adapter)
qgen.Install.RegisterPlugin(NewPrimaryKeySpitter()) // TODO: Do we really need to fill the spitter for every adapter? qgen.Install.AddPlugins(NewPrimaryKeySpitter()) // TODO: Do we really need to fill the spitter for every adapter?
err := writeStatements(adapter) err := writeStatements(adapter)
if err != nil { if err != nil {
@ -75,16 +75,6 @@ func writeStatements(adapter qgen.Adapter) error {
return err return err
} }
/*err = writeReplaces(adapter)
if err != nil {
return err
}
err = writeUpserts(adapter)
if err != nil {
return err
}*/
err = writeUpdates(adapter) err = writeUpdates(adapter)
if err != nil { if err != nil {
return err return err
@ -125,6 +115,8 @@ func seedTables(adapter qgen.Adapter) error {
qgen.Install.SimpleInsert("settings", "name, content, type, constraints", "'activation_type','1','list','1-3'") qgen.Install.SimpleInsert("settings", "name, content, type, constraints", "'activation_type','1','list','1-3'")
qgen.Install.SimpleInsert("settings", "name, content, type", "'bigpost_min_words','250','int'") qgen.Install.SimpleInsert("settings", "name, content, type", "'bigpost_min_words','250','int'")
qgen.Install.SimpleInsert("settings", "name, content, type", "'megapost_min_words','1000','int'") qgen.Install.SimpleInsert("settings", "name, content, type", "'megapost_min_words','1000','int'")
qgen.Install.SimpleInsert("settings", "name, content, type", "'about_segment_title','','text'")
qgen.Install.SimpleInsert("settings", "name, content, type", "'about_segment_body','','text'")
qgen.Install.SimpleInsert("themes", "uname, default", "'tempra-simple',1") qgen.Install.SimpleInsert("themes", "uname, default", "'tempra-simple',1")
qgen.Install.SimpleInsert("emails", "email, uid, validated", "'admin@localhost',1,1") // ? - Use a different default email or let the admin input it during installation? qgen.Install.SimpleInsert("emails", "email, uid, validated", "'admin@localhost',1,1") // ? - Use a different default email or let the admin input it during installation?
@ -168,6 +160,7 @@ func seedTables(adapter qgen.Adapter) error {
CloseTopic CloseTopic
*/ */
// TODO: Set the permissions on a struct and then serialize the struct and insert that instead of writing raw JSON
qgen.Install.SimpleInsert("users_groups", "name, permissions, plugin_perms, is_mod, is_admin, tag", `'Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditUserGroupAdmin":false,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"EditGroupAdmin":false,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"UploadFiles":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true}','{}',1,1,"Admin"`) qgen.Install.SimpleInsert("users_groups", "name, permissions, plugin_perms, is_mod, is_admin, tag", `'Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditUserGroupAdmin":false,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"EditGroupAdmin":false,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"UploadFiles":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true}','{}',1,1,"Admin"`)
qgen.Install.SimpleInsert("users_groups", "name, permissions, plugin_perms, is_mod, tag", `'Moderator','{"BanUsers":true,"ActivateUsers":false,"EditUser":true,"EditUserEmail":false,"EditUserGroup":true,"ViewIPs":true,"UploadFiles":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true}','{}',1,"Mod"`) qgen.Install.SimpleInsert("users_groups", "name, permissions, plugin_perms, is_mod, tag", `'Moderator','{"BanUsers":true,"ActivateUsers":false,"EditUser":true,"EditUserEmail":false,"EditUserGroup":true,"ViewIPs":true,"UploadFiles":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true}','{}',1,"Mod"`)
@ -226,8 +219,6 @@ func writeSelects(adapter qgen.Adapter) error {
build.Select("getPassword").Table("users").Columns("password, salt").Where("uid = ?").Parse() build.Select("getPassword").Table("users").Columns("password, salt").Where("uid = ?").Parse()
build.Select("getSettings").Table("settings").Columns("name, content, type").Parse()
build.Select("isPluginActive").Table("plugins").Columns("active").Where("uname = ?").Parse() build.Select("isPluginActive").Table("plugins").Columns("active").Where("uname = ?").Parse()
//build.Select("isPluginInstalled").Table("plugins").Columns("installed").Where("uname = ?").Parse() //build.Select("isPluginInstalled").Table("plugins").Columns("installed").Where("uname = ?").Parse()
@ -314,25 +305,6 @@ func writeInserts(adapter qgen.Adapter) error {
return nil return nil
} }
func writeReplaces(adapter qgen.Adapter) (err error) {
return nil
}
// ! Upserts are broken atm
/*func writeUpserts(adapter qgen.Adapter) (err error) {
_, err = adapter.SimpleUpsert("addForumPermsToGroup", "forums_permissions", "gid, fid, preset, permissions", "?,?,?,?", "gid = ? AND fid = ?")
if err != nil {
return err
}
_, err = adapter.SimpleUpsert("replaceScheduleGroup", "users_groups_scheduler", "uid, set_group, issued_by, issued_at, revert_at, temporary", "?,?,?,UTC_TIMESTAMP(),?,?", "uid = ?")
if err != nil {
return err
}
return nil
}*/
func writeUpdates(adapter qgen.Adapter) error { func writeUpdates(adapter qgen.Adapter) error {
build := adapter.Builder() build := adapter.Builder()
@ -340,8 +312,6 @@ func writeUpdates(adapter qgen.Adapter) error {
build.Update("editProfileReply").Table("users_replies").Set("content = ?, parsed_content = ?").Where("rid = ?").Parse() build.Update("editProfileReply").Table("users_replies").Set("content = ?, parsed_content = ?").Where("rid = ?").Parse()
build.Update("updateSetting").Table("settings").Set("content = ?").Where("name = ?").Parse()
build.Update("updatePlugin").Table("plugins").Set("active = ?").Where("uname = ?").Parse() build.Update("updatePlugin").Table("plugins").Set("active = ?").Where("uname = ?").Parse()
build.Update("updatePluginInstall").Table("plugins").Set("installed = ?").Where("uname = ?").Parse() build.Update("updatePluginInstall").Table("plugins").Set("installed = ?").Where("uname = ?").Parse()
@ -385,8 +355,6 @@ func writeDeletes(adapter qgen.Adapter) error {
func writeSimpleCounts(adapter qgen.Adapter) error { func writeSimpleCounts(adapter qgen.Adapter) error {
adapter.SimpleCount("reportExists", "topics", "data = ? AND data != '' AND parentID = 1", "") adapter.SimpleCount("reportExists", "topics", "data = ? AND data != '' AND parentID = 1", "")
adapter.SimpleCount("modlogCount", "moderation_logs", "", "")
return nil return nil
} }

View File

@ -14,7 +14,8 @@ func NewPrimaryKeySpitter() *PrimaryKeySpitter {
func (spit *PrimaryKeySpitter) Hook(name string, args ...interface{}) error { func (spit *PrimaryKeySpitter) Hook(name string, args ...interface{}) error {
if name == "CreateTableStart" { if name == "CreateTableStart" {
var found string var found string
for _, key := range args[4].([]qgen.DBTableKey) { var table = args[0].(*qgen.DB_Install_Table)
for _, key := range table.Keys {
if key.Type == "primary" { if key.Type == "primary" {
expl := strings.Split(key.Columns, ",") expl := strings.Split(key.Columns, ",")
if len(expl) > 1 { if len(expl) > 1 {
@ -23,7 +24,7 @@ func (spit *PrimaryKeySpitter) Hook(name string, args ...interface{}) error {
found = key.Columns found = key.Columns
} }
if found != "" { if found != "" {
table := args[0].(string) table := table.Name
spit.keys[table] = found spit.keys[table] = found
} }
} }

View File

@ -68,8 +68,8 @@ func buildPanelRoutes() {
Action("routePanelForumsEditPermsSubmit", "/panel/forums/edit/perms/submit/", "extra_data"), Action("routePanelForumsEditPermsSubmit", "/panel/forums/edit/perms/submit/", "extra_data"),
View("routePanelSettings", "/panel/settings/"), View("routePanelSettings", "/panel/settings/"),
View("routePanelSetting", "/panel/settings/edit/", "extra_data"), View("routePanelSettingEdit", "/panel/settings/edit/", "extra_data"),
Action("routePanelSettingEdit", "/panel/settings/edit/submit/", "extra_data"), Action("routePanelSettingEditSubmit", "/panel/settings/edit/submit/", "extra_data"),
View("routePanelWordFilters", "/panel/settings/word-filters/"), View("routePanelWordFilters", "/panel/settings/word-filters/"),
Action("routePanelWordFiltersCreate", "/panel/settings/word-filters/create/"), Action("routePanelWordFiltersCreate", "/panel/settings/word-filters/create/"),

View File

@ -46,7 +46,7 @@ func routeStatic(w http.ResponseWriter, r *http.Request) {
file, ok := common.StaticFiles[r.URL.Path] file, ok := common.StaticFiles[r.URL.Path]
if !ok { if !ok {
if common.Dev.DebugMode { if common.Dev.DebugMode {
log.Print("Failed to find '" + r.URL.Path + "'") log.Printf("Failed to find '%s'", r.URL.Path)
} }
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
return return
@ -103,7 +103,7 @@ func routeOverview(w http.ResponseWriter, r *http.Request, user common.User) com
} }
common.BuildWidgets("overview", nil, headerVars, r) common.BuildWidgets("overview", nil, headerVars, r)
pi := common.Page{"Overview", user, headerVars, tList, nil} pi := common.Page{common.GetTitlePhrase("overview"), user, headerVars, tList, nil}
if common.PreRenderHooks["pre_render_overview"] != nil { if common.PreRenderHooks["pre_render_overview"] != nil {
if common.RunPreRenderHook("pre_render_overview", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_overview", w, r, &user, &pi) {
return nil return nil
@ -129,7 +129,7 @@ func routeCustomPage(w http.ResponseWriter, r *http.Request, user common.User) c
} }
common.BuildWidgets("custom_page", name, headerVars, r) common.BuildWidgets("custom_page", name, headerVars, r)
pi := common.Page{"Page", user, headerVars, tList, nil} pi := common.Page{common.GetTitlePhrase("page"), user, headerVars, tList, nil}
if common.PreRenderHooks["pre_render_custom_page"] != nil { if common.PreRenderHooks["pre_render_custom_page"] != nil {
if common.RunPreRenderHook("pre_render_custom_page", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_custom_page", w, r, &user, &pi) {
return nil return nil
@ -152,7 +152,7 @@ func routeTopics(w http.ResponseWriter, r *http.Request, user common.User) commo
// TODO: Add a function for the qlist stuff // TODO: Add a function for the qlist stuff
var qlist string var qlist string
group, err := common.Gstore.Get(user.Group) group, err := common.Groups.Get(user.Group)
if err != nil { if err != nil {
log.Printf("Group #%d doesn't exist despite being used by common.User #%d", user.Group, user.ID) log.Printf("Group #%d doesn't exist despite being used by common.User #%d", user.Group, user.ID)
return common.LocalError("Something weird happened", w, r, user) return common.LocalError("Something weird happened", w, r, user)
@ -161,7 +161,7 @@ func routeTopics(w http.ResponseWriter, r *http.Request, user common.User) commo
// TODO: Make CanSee a method on *Group with a canSee field? // TODO: Make CanSee a method on *Group with a canSee field?
var canSee []int var canSee []int
if user.IsSuperAdmin { if user.IsSuperAdmin {
canSee, err = common.Fstore.GetAllVisibleIDs() canSee, err = common.Forums.GetAllVisibleIDs()
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -174,7 +174,7 @@ func routeTopics(w http.ResponseWriter, r *http.Request, user common.User) commo
var argList []interface{} var argList []interface{}
for _, fid := range canSee { for _, fid := range canSee {
forum := common.Fstore.DirtyGet(fid) forum := common.Forums.DirtyGet(fid)
if forum.Name != "" && forum.Active { if forum.Name != "" && forum.Active {
if forum.ParentType == "" || forum.ParentType == "forum" { if forum.ParentType == "" || forum.ParentType == "forum" {
// Optimise Quick Topic away for guests // Optimise Quick Topic away for guests
@ -251,7 +251,7 @@ func routeTopics(w http.ResponseWriter, r *http.Request, user common.User) commo
topicItem.Link = common.BuildTopicURL(common.NameToSlug(topicItem.Title), topicItem.ID) topicItem.Link = common.BuildTopicURL(common.NameToSlug(topicItem.Title), topicItem.ID)
forum := common.Fstore.DirtyGet(topicItem.ParentID) forum := common.Forums.DirtyGet(topicItem.ParentID)
topicItem.ForumName = forum.Name topicItem.ForumName = forum.Name
topicItem.ForumLink = forum.Link topicItem.ForumLink = forum.Link
@ -291,13 +291,13 @@ func routeTopics(w http.ResponseWriter, r *http.Request, user common.User) commo
topicItem.LastUser = userList[topicItem.LastReplyBy] topicItem.LastUser = userList[topicItem.LastReplyBy]
} }
pi := common.TopicsPage{"All Topics", user, headerVars, topicList, forumList, common.Config.DefaultForum} pi := common.TopicsPage{common.GetTitlePhrase("topics"), user, headerVars, topicList, forumList, common.Config.DefaultForum}
if common.PreRenderHooks["pre_render_topic_list"] != nil { if common.PreRenderHooks["pre_render_topic_list"] != nil {
if common.RunPreRenderHook("pre_render_topic_list", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_topic_list", w, r, &user, &pi) {
return nil return nil
} }
} }
err = common.RunThemeTemplate(headerVars.ThemeName, "topics", pi, w) err = common.RunThemeTemplate(headerVars.Theme.Name, "topics", pi, w)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -327,7 +327,7 @@ func routeForum(w http.ResponseWriter, r *http.Request, user common.User, sfid s
} }
// TODO: Fix this double-check // TODO: Fix this double-check
forum, err := common.Fstore.Get(fid) forum, err := common.Forums.Get(fid)
if err == ErrNoRows { if err == ErrNoRows {
return common.NotFound(w, r) return common.NotFound(w, r)
} else if err != nil { } else if err != nil {
@ -408,7 +408,7 @@ func routeForum(w http.ResponseWriter, r *http.Request, user common.User, sfid s
return nil return nil
} }
} }
err = common.RunThemeTemplate(headerVars.ThemeName, "forum", pi, w) err = common.RunThemeTemplate(headerVars.Theme.Name, "forum", pi, w)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -426,13 +426,12 @@ func routeForums(w http.ResponseWriter, r *http.Request, user common.User) commo
var forumList []common.Forum var forumList []common.Forum
var canSee []int var canSee []int
if user.IsSuperAdmin { if user.IsSuperAdmin {
canSee, err = common.Fstore.GetAllVisibleIDs() canSee, err = common.Forums.GetAllVisibleIDs()
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
//log.Print("canSee ", canSee)
} else { } else {
group, err := common.Gstore.Get(user.Group) group, err := common.Groups.Get(user.Group)
if err != nil { if err != nil {
log.Printf("Group #%d doesn't exist despite being used by common.User #%d", user.Group, user.ID) log.Printf("Group #%d doesn't exist despite being used by common.User #%d", user.Group, user.ID)
return common.LocalError("Something weird happened", w, r, user) return common.LocalError("Something weird happened", w, r, user)
@ -442,7 +441,7 @@ func routeForums(w http.ResponseWriter, r *http.Request, user common.User) commo
for _, fid := range canSee { for _, fid := range canSee {
// Avoid data races by copying the struct into something we can freely mold without worrying about breaking something somewhere else // Avoid data races by copying the struct into something we can freely mold without worrying about breaking something somewhere else
var forum = common.Fstore.DirtyGet(fid).Copy() var forum = common.Forums.DirtyGet(fid).Copy()
if forum.ParentID == 0 && forum.Name != "" && forum.Active { if forum.ParentID == 0 && forum.Name != "" && forum.Active {
if forum.LastTopicID != 0 { if forum.LastTopicID != 0 {
if forum.LastTopic.ID != 0 && forum.LastReplyer.ID != 0 { if forum.LastTopic.ID != 0 && forum.LastReplyer.ID != 0 {
@ -460,13 +459,13 @@ func routeForums(w http.ResponseWriter, r *http.Request, user common.User) commo
} }
} }
pi := common.ForumsPage{"Forum List", user, headerVars, forumList} pi := common.ForumsPage{common.GetTitlePhrase("forums"), user, headerVars, forumList}
if common.PreRenderHooks["pre_render_forum_list"] != nil { if common.PreRenderHooks["pre_render_forum_list"] != nil {
if common.RunPreRenderHook("pre_render_forum_list", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_forum_list", w, r, &user, &pi) {
return nil return nil
} }
} }
err = common.RunThemeTemplate(headerVars.ThemeName, "forums", pi, w) err = common.RunThemeTemplate(headerVars.Theme.Name, "forums", pi, w)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -481,6 +480,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user common.User) comm
page, _ = strconv.Atoi(r.FormValue("page")) page, _ = strconv.Atoi(r.FormValue("page"))
// SEO URLs... // SEO URLs...
// TODO: Make a shared function for this
halves := strings.Split(r.URL.Path[len("/topic/"):], ".") halves := strings.Split(r.URL.Path[len("/topic/"):], ".")
if len(halves) < 2 { if len(halves) < 2 {
halves = append(halves, halves[0]) halves = append(halves, halves[0])
@ -520,7 +520,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user common.User) comm
user.Perms.CreateReply = false user.Perms.CreateReply = false
} }
postGroup, err := common.Gstore.Get(topic.Group) postGroup, err := common.Groups.Get(topic.Group)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -574,7 +574,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user common.User) comm
replyItem.ContentHtml = common.ParseMessage(replyItem.Content, topic.ParentID, "forums") replyItem.ContentHtml = common.ParseMessage(replyItem.Content, topic.ParentID, "forums")
replyItem.ContentLines = strings.Count(replyItem.Content, "\n") replyItem.ContentLines = strings.Count(replyItem.Content, "\n")
postGroup, err = common.Gstore.Get(replyItem.Group) postGroup, err = common.Groups.Get(replyItem.Group)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -635,7 +635,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user common.User) comm
return nil return nil
} }
} }
err = common.RunThemeTemplate(headerVars.ThemeName, "topic", tpage, w) err = common.RunThemeTemplate(headerVars.Theme.Name, "topic", tpage, w)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -671,6 +671,7 @@ func routeProfile(w http.ResponseWriter, r *http.Request, user common.User) comm
puser = &user puser = &user
} else { } else {
// Fetch the user data // Fetch the user data
// TODO: Add a shared function for checking for ErrNoRows and internal erroring if it's not that case?
puser, err = common.Users.Get(pid) puser, err = common.Users.Get(pid)
if err == ErrNoRows { if err == ErrNoRows {
return common.NotFound(w, r) return common.NotFound(w, r)
@ -692,7 +693,7 @@ func routeProfile(w http.ResponseWriter, r *http.Request, user common.User) comm
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
group, err := common.Gstore.Get(replyGroup) group, err := common.Groups.Get(replyGroup)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -733,6 +734,7 @@ func routeProfile(w http.ResponseWriter, r *http.Request, user common.User) comm
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
// TODO: Add a phrase for this title
ppage := common.ProfilePage{puser.Name + "'s Profile", user, headerVars, replyList, *puser} ppage := common.ProfilePage{puser.Name + "'s Profile", user, headerVars, replyList, *puser}
if common.PreRenderHooks["pre_render_profile"] != nil { if common.PreRenderHooks["pre_render_profile"] != nil {
if common.RunPreRenderHook("pre_render_profile", w, r, &user, &ppage) { if common.RunPreRenderHook("pre_render_profile", w, r, &user, &ppage) {
@ -740,7 +742,7 @@ func routeProfile(w http.ResponseWriter, r *http.Request, user common.User) comm
} }
} }
err = common.RunThemeTemplate(headerVars.ThemeName, "profile", ppage, w) err = common.RunThemeTemplate(headerVars.Theme.Name, "profile", ppage, w)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -755,7 +757,7 @@ func routeLogin(w http.ResponseWriter, r *http.Request, user common.User) common
if user.Loggedin { if user.Loggedin {
return common.LocalError("You're already logged in.", w, r, user) return common.LocalError("You're already logged in.", w, r, user)
} }
pi := common.Page{"Login", user, headerVars, tList, nil} pi := common.Page{common.GetTitlePhrase("login"), user, headerVars, tList, nil}
if common.PreRenderHooks["pre_render_login"] != nil { if common.PreRenderHooks["pre_render_login"] != nil {
if common.RunPreRenderHook("pre_render_login", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_login", w, r, &user, &pi) {
return nil return nil
@ -803,7 +805,7 @@ func routeLoginSubmit(w http.ResponseWriter, r *http.Request, user common.User)
common.Auth.SetCookies(w, uid, session) common.Auth.SetCookies(w, uid, session)
if user.IsAdmin { if user.IsAdmin {
// Is this error check reundant? We already check for the error in PreRoute for the same IP // Is this error check redundant? We already check for the error in PreRoute for the same IP
// TODO: Should we be logging this? // TODO: Should we be logging this?
log.Printf("#%d has logged in with IP %s", uid, user.LastIP) log.Printf("#%d has logged in with IP %s", uid, user.LastIP)
} }
@ -819,7 +821,7 @@ func routeRegister(w http.ResponseWriter, r *http.Request, user common.User) com
if user.Loggedin { if user.Loggedin {
return common.LocalError("You're already logged in.", w, r, user) return common.LocalError("You're already logged in.", w, r, user)
} }
pi := common.Page{"Registration", user, headerVars, tList, nil} pi := common.Page{common.GetTitlePhrase("register"), user, headerVars, tList, nil}
if common.PreRenderHooks["pre_render_register"] != nil { if common.PreRenderHooks["pre_render_register"] != nil {
if common.RunPreRenderHook("pre_render_register", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_register", w, r, &user, &pi) {
return nil return nil
@ -937,10 +939,12 @@ func routeChangeTheme(w http.ResponseWriter, r *http.Request, user common.User)
return nil return nil
} }
// TODO: We don't need support XML here to support sitemaps, we could handle those elsewhere // TODO: Refactor this
var phraseLoginAlerts = []byte(`{"msgs":[{"msg":"Login to see your alerts","path":"/accounts/login"}]}`) var phraseLoginAlerts = []byte(`{"msgs":[{"msg":"Login to see your alerts","path":"/accounts/login"}]}`)
// TODO: Refactor this endpoint
func routeAPI(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routeAPI(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
// TODO: Don't make this too JSON dependent so that we can swap in newer more efficient formats
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
@ -1008,11 +1012,6 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user common.User) common.R
msglist = msglist[0 : len(msglist)-1] msglist = msglist[0 : len(msglist)-1]
} }
_, _ = w.Write([]byte(`{"msgs":[` + msglist + `],"msgCount":` + strconv.Itoa(msgCount) + `}`)) _, _ = w.Write([]byte(`{"msgs":[` + msglist + `],"msgCount":` + strconv.Itoa(msgCount) + `}`))
//log.Print(`{"msgs":[` + msglist + `],"msgCount":` + strconv.Itoa(msgCount) + `}`)
//case "topics":
//case "forums":
//case "users":
//case "pages":
default: default:
return common.PreErrorJS("Invalid Module", w, r) return common.PreErrorJS("Invalid Module", w, r)
} }

View File

@ -3,6 +3,8 @@ INSERT INTO [settings] ([name],[content],[type]) VALUES ('url_tags','1','bool');
INSERT INTO [settings] ([name],[content],[type],[constraints]) VALUES ('activation_type','1','list','1-3'); INSERT INTO [settings] ([name],[content],[type],[constraints]) VALUES ('activation_type','1','list','1-3');
INSERT INTO [settings] ([name],[content],[type]) VALUES ('bigpost_min_words','250','int'); INSERT INTO [settings] ([name],[content],[type]) VALUES ('bigpost_min_words','250','int');
INSERT INTO [settings] ([name],[content],[type]) VALUES ('megapost_min_words','1000','int'); INSERT INTO [settings] ([name],[content],[type]) VALUES ('megapost_min_words','1000','int');
INSERT INTO [settings] ([name],[content],[type]) VALUES ('about_segment_title','','text');
INSERT INTO [settings] ([name],[content],[type]) VALUES ('about_segment_body','','text');
INSERT INTO [themes] ([uname],[default]) VALUES ('tempra-simple',1); INSERT INTO [themes] ([uname],[default]) VALUES ('tempra-simple',1);
INSERT INTO [emails] ([email],[uid],[validated]) VALUES ('admin@localhost',1,1); INSERT INTO [emails] ([email],[uid],[validated]) VALUES ('admin@localhost',1,1);
INSERT INTO [users_groups] ([name],[permissions],[plugin_perms],[is_mod],[is_admin],[tag]) VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditUserGroupAdmin":false,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"EditGroupAdmin":false,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"UploadFiles":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true}','{}',1,1,'Admin'); INSERT INTO [users_groups] ([name],[permissions],[plugin_perms],[is_mod],[is_admin],[tag]) VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditUserGroupAdmin":false,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"EditGroupAdmin":false,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"UploadFiles":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true}','{}',1,1,'Admin');

View File

@ -3,6 +3,8 @@ INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('url_tags','1','bool');
INSERT INTO `settings`(`name`,`content`,`type`,`constraints`) VALUES ('activation_type','1','list','1-3'); INSERT INTO `settings`(`name`,`content`,`type`,`constraints`) VALUES ('activation_type','1','list','1-3');
INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('bigpost_min_words','250','int'); INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('bigpost_min_words','250','int');
INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('megapost_min_words','1000','int'); INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('megapost_min_words','1000','int');
INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('about_segment_title','','text');
INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('about_segment_body','','text');
INSERT INTO `themes`(`uname`,`default`) VALUES ('tempra-simple',1); INSERT INTO `themes`(`uname`,`default`) VALUES ('tempra-simple',1);
INSERT INTO `emails`(`email`,`uid`,`validated`) VALUES ('admin@localhost',1,1); INSERT INTO `emails`(`email`,`uid`,`validated`) VALUES ('admin@localhost',1,1);
INSERT INTO `users_groups`(`name`,`permissions`,`plugin_perms`,`is_mod`,`is_admin`,`tag`) VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditUserGroupAdmin":false,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"EditGroupAdmin":false,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"UploadFiles":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true}','{}',1,1,'Admin'); INSERT INTO `users_groups`(`name`,`permissions`,`plugin_perms`,`is_mod`,`is_admin`,`tag`) VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditUserGroupAdmin":false,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"EditGroupAdmin":false,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"UploadFiles":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true}','{}',1,1,'Admin');

View File

@ -27,3 +27,5 @@
; ;
; ;
; ;
;
;

View File

@ -22,7 +22,7 @@ w.Write([]byte(tmpl_forum_vars.Title))
w.Write(header_1) w.Write(header_1)
w.Write([]byte(tmpl_forum_vars.Header.Site.Name)) w.Write([]byte(tmpl_forum_vars.Header.Site.Name))
w.Write(header_2) w.Write(header_2)
w.Write([]byte(tmpl_forum_vars.Header.ThemeName)) w.Write([]byte(tmpl_forum_vars.Header.Theme.Name))
w.Write(header_3) w.Write(header_3)
if len(tmpl_forum_vars.Header.Stylesheets) != 0 { if len(tmpl_forum_vars.Header.Stylesheets) != 0 {
for _, item := range tmpl_forum_vars.Header.Stylesheets { for _, item := range tmpl_forum_vars.Header.Stylesheets {
@ -203,28 +203,37 @@ w.Write(forum_57)
w.Write(forum_58) w.Write(forum_58)
} }
w.Write(forum_59) w.Write(forum_59)
if tmpl_forum_vars.Header.Theme.AboutSegment {
w.Write(footer_0) w.Write(footer_0)
dispInt := tmpl_forum_vars.Header.Settings["about_segment_title"]
w.Write([]byte(dispInt.(string)))
w.Write(footer_1)
dispInt = tmpl_forum_vars.Header.Settings["about_segment_body"]
w.Write([]byte(dispInt.(string)))
w.Write(footer_2)
}
w.Write(footer_3)
if len(tmpl_forum_vars.Header.Themes) != 0 { if len(tmpl_forum_vars.Header.Themes) != 0 {
for _, item := range tmpl_forum_vars.Header.Themes { for _, item := range tmpl_forum_vars.Header.Themes {
if !item.HideFromThemes { if !item.HideFromThemes {
w.Write(footer_1)
w.Write([]byte(item.Name))
w.Write(footer_2)
if tmpl_forum_vars.Header.ThemeName == item.Name {
w.Write(footer_3)
}
w.Write(footer_4) w.Write(footer_4)
w.Write([]byte(item.FriendlyName)) w.Write([]byte(item.Name))
w.Write(footer_5) w.Write(footer_5)
} if tmpl_forum_vars.Header.Theme.Name == item.Name {
}
}
w.Write(footer_6) w.Write(footer_6)
if tmpl_forum_vars.Header.Widgets.RightSidebar != "" { }
w.Write(footer_7) w.Write(footer_7)
w.Write([]byte(string(tmpl_forum_vars.Header.Widgets.RightSidebar))) w.Write([]byte(item.FriendlyName))
w.Write(footer_8) w.Write(footer_8)
} }
}
}
w.Write(footer_9) w.Write(footer_9)
if tmpl_forum_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_10)
w.Write([]byte(string(tmpl_forum_vars.Header.Widgets.RightSidebar)))
w.Write(footer_11)
}
w.Write(footer_12)
return nil return nil
} }

View File

@ -21,7 +21,7 @@ w.Write([]byte(tmpl_forums_vars.Title))
w.Write(header_1) w.Write(header_1)
w.Write([]byte(tmpl_forums_vars.Header.Site.Name)) w.Write([]byte(tmpl_forums_vars.Header.Site.Name))
w.Write(header_2) w.Write(header_2)
w.Write([]byte(tmpl_forums_vars.Header.ThemeName)) w.Write([]byte(tmpl_forums_vars.Header.Theme.Name))
w.Write(header_3) w.Write(header_3)
if len(tmpl_forums_vars.Header.Stylesheets) != 0 { if len(tmpl_forums_vars.Header.Stylesheets) != 0 {
for _, item := range tmpl_forums_vars.Header.Stylesheets { for _, item := range tmpl_forums_vars.Header.Stylesheets {
@ -118,28 +118,37 @@ w.Write(forums_18)
w.Write(forums_19) w.Write(forums_19)
} }
w.Write(forums_20) w.Write(forums_20)
if tmpl_forums_vars.Header.Theme.AboutSegment {
w.Write(footer_0) w.Write(footer_0)
dispInt := tmpl_forums_vars.Header.Settings["about_segment_title"]
w.Write([]byte(dispInt.(string)))
w.Write(footer_1)
dispInt = tmpl_forums_vars.Header.Settings["about_segment_body"]
w.Write([]byte(dispInt.(string)))
w.Write(footer_2)
}
w.Write(footer_3)
if len(tmpl_forums_vars.Header.Themes) != 0 { if len(tmpl_forums_vars.Header.Themes) != 0 {
for _, item := range tmpl_forums_vars.Header.Themes { for _, item := range tmpl_forums_vars.Header.Themes {
if !item.HideFromThemes { if !item.HideFromThemes {
w.Write(footer_1)
w.Write([]byte(item.Name))
w.Write(footer_2)
if tmpl_forums_vars.Header.ThemeName == item.Name {
w.Write(footer_3)
}
w.Write(footer_4) w.Write(footer_4)
w.Write([]byte(item.FriendlyName)) w.Write([]byte(item.Name))
w.Write(footer_5) w.Write(footer_5)
} if tmpl_forums_vars.Header.Theme.Name == item.Name {
}
}
w.Write(footer_6) w.Write(footer_6)
if tmpl_forums_vars.Header.Widgets.RightSidebar != "" { }
w.Write(footer_7) w.Write(footer_7)
w.Write([]byte(string(tmpl_forums_vars.Header.Widgets.RightSidebar))) w.Write([]byte(item.FriendlyName))
w.Write(footer_8) w.Write(footer_8)
} }
}
}
w.Write(footer_9) w.Write(footer_9)
if tmpl_forums_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_10)
w.Write([]byte(string(tmpl_forums_vars.Header.Widgets.RightSidebar)))
w.Write(footer_11)
}
w.Write(footer_12)
return nil return nil
} }

View File

@ -20,7 +20,7 @@ w.Write([]byte(tmpl_guilds_guild_list_vars.Title))
w.Write(header_1) w.Write(header_1)
w.Write([]byte(tmpl_guilds_guild_list_vars.Header.Site.Name)) w.Write([]byte(tmpl_guilds_guild_list_vars.Header.Site.Name))
w.Write(header_2) w.Write(header_2)
w.Write([]byte(tmpl_guilds_guild_list_vars.Header.ThemeName)) w.Write([]byte(tmpl_guilds_guild_list_vars.Header.Theme.Name))
w.Write(header_3) w.Write(header_3)
if len(tmpl_guilds_guild_list_vars.Header.Stylesheets) != 0 { if len(tmpl_guilds_guild_list_vars.Header.Stylesheets) != 0 {
for _, item := range tmpl_guilds_guild_list_vars.Header.Stylesheets { for _, item := range tmpl_guilds_guild_list_vars.Header.Stylesheets {
@ -91,28 +91,37 @@ w.Write(guilds_guild_list_6)
w.Write(guilds_guild_list_7) w.Write(guilds_guild_list_7)
} }
w.Write(guilds_guild_list_8) w.Write(guilds_guild_list_8)
if tmpl_guilds_guild_list_vars.Header.Theme.AboutSegment {
w.Write(footer_0) w.Write(footer_0)
dispInt := tmpl_guilds_guild_list_vars.Header.Settings["about_segment_title"]
w.Write([]byte(dispInt.(string)))
w.Write(footer_1)
dispInt = tmpl_guilds_guild_list_vars.Header.Settings["about_segment_body"]
w.Write([]byte(dispInt.(string)))
w.Write(footer_2)
}
w.Write(footer_3)
if len(tmpl_guilds_guild_list_vars.Header.Themes) != 0 { if len(tmpl_guilds_guild_list_vars.Header.Themes) != 0 {
for _, item := range tmpl_guilds_guild_list_vars.Header.Themes { for _, item := range tmpl_guilds_guild_list_vars.Header.Themes {
if !item.HideFromThemes { if !item.HideFromThemes {
w.Write(footer_1)
w.Write([]byte(item.Name))
w.Write(footer_2)
if tmpl_guilds_guild_list_vars.Header.ThemeName == item.Name {
w.Write(footer_3)
}
w.Write(footer_4) w.Write(footer_4)
w.Write([]byte(item.FriendlyName)) w.Write([]byte(item.Name))
w.Write(footer_5) w.Write(footer_5)
} if tmpl_guilds_guild_list_vars.Header.Theme.Name == item.Name {
}
}
w.Write(footer_6) w.Write(footer_6)
if tmpl_guilds_guild_list_vars.Header.Widgets.RightSidebar != "" { }
w.Write(footer_7) w.Write(footer_7)
w.Write([]byte(string(tmpl_guilds_guild_list_vars.Header.Widgets.RightSidebar))) w.Write([]byte(item.FriendlyName))
w.Write(footer_8) w.Write(footer_8)
} }
}
}
w.Write(footer_9) w.Write(footer_9)
if tmpl_guilds_guild_list_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_10)
w.Write([]byte(string(tmpl_guilds_guild_list_vars.Header.Widgets.RightSidebar)))
w.Write(footer_11)
}
w.Write(footer_12)
return nil return nil
} }

View File

@ -264,27 +264,36 @@ var topic_100 = []byte(`
</main> </main>
`) `)
var footer_0 = []byte(`<div class="footer"> var footer_0 = []byte(`<div class="about">
<div id="poweredBy">Powered by Gosora - <span>Made with love by Azareal</span></div> <a id="aboutTitle">`)
var footer_1 = []byte(`</a>
<span id="aboutDesc">`)
var footer_2 = []byte(`</span>
</div>`)
var footer_3 = []byte(`
<div class="footer">
<div id="poweredBy">
<a id="poweredByName" href="https://github.com/Azareal/Gosora">Powered by Gosora</a><span id="poweredByDash"> - </span><span id="poweredByMaker">Made with love by Azareal</span>
</div>
<form action="/theme/" method="post"> <form action="/theme/" method="post">
<div id="themeSelector" style="float: right;"> <div id="themeSelector" style="float: right;">
<select id="themeSelectorSelect" name="themeSelector" aria-label="Change the site's appearance"> <select id="themeSelectorSelect" name="themeSelector" aria-label="Change the site's appearance">
`) `)
var footer_1 = []byte(`<option val="`) var footer_4 = []byte(`<option val="`)
var footer_2 = []byte(`"`) var footer_5 = []byte(`"`)
var footer_3 = []byte(` selected`) var footer_6 = []byte(` selected`)
var footer_4 = []byte(`>`) var footer_7 = []byte(`>`)
var footer_5 = []byte(`</option>`) var footer_8 = []byte(`</option>`)
var footer_6 = []byte(` var footer_9 = []byte(`
</select> </select>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
`) `)
var footer_7 = []byte(`<aside class="sidebar">`) var footer_10 = []byte(`<aside class="sidebar">`)
var footer_8 = []byte(`</aside>`) var footer_11 = []byte(`</aside>`)
var footer_9 = []byte(` var footer_12 = []byte(`
<div style="clear: both;"></div> <div style="clear: both;"></div>
</div> </div>
</div> </div>
@ -498,7 +507,7 @@ var topic_alt_104 = []byte(`
`) `)
var profile_0 = []byte(` var profile_0 = []byte(`
<div id="profile_container"> <div id="profile_container" class="colstack">
<div id="profile_left_lane" class="colstack_left"> <div id="profile_left_lane" class="colstack_left">
<!--<header class="colstack_item colstack_head rowhead"> <!--<header class="colstack_item colstack_head rowhead">
@ -885,7 +894,8 @@ var forum_8 = []byte(`
<div id="forum_head_block" class="rowblock rowhead topic_list_title_block"> <div id="forum_head_block" class="rowblock rowhead topic_list_title_block">
<div class="rowitem forum_title`) <div class="rowitem forum_title`)
var forum_9 = []byte(` has_opt`) var forum_9 = []byte(` has_opt`)
var forum_10 = []byte(`"><h1>`) var forum_10 = []byte(`">
<h1>`)
var forum_11 = []byte(`</h1> var forum_11 = []byte(`</h1>
</div> </div>
`) `)
@ -921,7 +931,6 @@ var forum_18 = []byte(`
</div> </div>
</form> </form>
</div> </div>
`) `)
var forum_19 = []byte(` var forum_19 = []byte(`
<div id="forum_topic_create_form" class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form"> <div id="forum_topic_create_form" class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form">
@ -1018,7 +1027,6 @@ var forum_57 = []byte(`">Start one?</a>`)
var forum_58 = []byte(`</div>`) var forum_58 = []byte(`</div>`)
var forum_59 = []byte(` var forum_59 = []byte(`
</div> </div>
</main> </main>
`) `)
var guilds_guild_list_0 = []byte(` var guilds_guild_list_0 = []byte(`

View File

@ -3,9 +3,9 @@
// Code generated by Gosora. More below: // Code generated by Gosora. More below:
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */ /* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main package main
import "net/http"
import "./common" import "./common"
import "strconv" import "strconv"
import "net/http"
// nolint // nolint
func init() { func init() {
@ -22,7 +22,7 @@ w.Write([]byte(tmpl_profile_vars.Title))
w.Write(header_1) w.Write(header_1)
w.Write([]byte(tmpl_profile_vars.Header.Site.Name)) w.Write([]byte(tmpl_profile_vars.Header.Site.Name))
w.Write(header_2) w.Write(header_2)
w.Write([]byte(tmpl_profile_vars.Header.ThemeName)) w.Write([]byte(tmpl_profile_vars.Header.Theme.Name))
w.Write(header_3) w.Write(header_3)
if len(tmpl_profile_vars.Header.Stylesheets) != 0 { if len(tmpl_profile_vars.Header.Stylesheets) != 0 {
for _, item := range tmpl_profile_vars.Header.Stylesheets { for _, item := range tmpl_profile_vars.Header.Stylesheets {
@ -161,28 +161,37 @@ w.Write(profile_41)
} }
w.Write(profile_42) w.Write(profile_42)
w.Write(profile_43) w.Write(profile_43)
if tmpl_profile_vars.Header.Theme.AboutSegment {
w.Write(footer_0) w.Write(footer_0)
dispInt := tmpl_profile_vars.Header.Settings["about_segment_title"]
w.Write([]byte(dispInt.(string)))
w.Write(footer_1)
dispInt = tmpl_profile_vars.Header.Settings["about_segment_body"]
w.Write([]byte(dispInt.(string)))
w.Write(footer_2)
}
w.Write(footer_3)
if len(tmpl_profile_vars.Header.Themes) != 0 { if len(tmpl_profile_vars.Header.Themes) != 0 {
for _, item := range tmpl_profile_vars.Header.Themes { for _, item := range tmpl_profile_vars.Header.Themes {
if !item.HideFromThemes { if !item.HideFromThemes {
w.Write(footer_1)
w.Write([]byte(item.Name))
w.Write(footer_2)
if tmpl_profile_vars.Header.ThemeName == item.Name {
w.Write(footer_3)
}
w.Write(footer_4) w.Write(footer_4)
w.Write([]byte(item.FriendlyName)) w.Write([]byte(item.Name))
w.Write(footer_5) w.Write(footer_5)
} if tmpl_profile_vars.Header.Theme.Name == item.Name {
}
}
w.Write(footer_6) w.Write(footer_6)
if tmpl_profile_vars.Header.Widgets.RightSidebar != "" { }
w.Write(footer_7) w.Write(footer_7)
w.Write([]byte(string(tmpl_profile_vars.Header.Widgets.RightSidebar))) w.Write([]byte(item.FriendlyName))
w.Write(footer_8) w.Write(footer_8)
} }
}
}
w.Write(footer_9) w.Write(footer_9)
if tmpl_profile_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_10)
w.Write([]byte(string(tmpl_profile_vars.Header.Widgets.RightSidebar)))
w.Write(footer_11)
}
w.Write(footer_12)
return nil return nil
} }

View File

@ -3,9 +3,9 @@
// Code generated by Gosora. More below: // Code generated by Gosora. More below:
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */ /* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main package main
import "strconv"
import "net/http" import "net/http"
import "./common" import "./common"
import "strconv"
// nolint // nolint
func init() { func init() {
@ -22,7 +22,7 @@ w.Write([]byte(tmpl_topic_vars.Title))
w.Write(header_1) w.Write(header_1)
w.Write([]byte(tmpl_topic_vars.Header.Site.Name)) w.Write([]byte(tmpl_topic_vars.Header.Site.Name))
w.Write(header_2) w.Write(header_2)
w.Write([]byte(tmpl_topic_vars.Header.ThemeName)) w.Write([]byte(tmpl_topic_vars.Header.Theme.Name))
w.Write(header_3) w.Write(header_3)
if len(tmpl_topic_vars.Header.Stylesheets) != 0 { if len(tmpl_topic_vars.Header.Stylesheets) != 0 {
for _, item := range tmpl_topic_vars.Header.Stylesheets { for _, item := range tmpl_topic_vars.Header.Stylesheets {
@ -125,7 +125,7 @@ if tmpl_topic_vars.Topic.Avatar != "" {
w.Write(topic_22) w.Write(topic_22)
w.Write([]byte(tmpl_topic_vars.Topic.Avatar)) w.Write([]byte(tmpl_topic_vars.Topic.Avatar))
w.Write(topic_23) w.Write(topic_23)
w.Write([]byte(tmpl_topic_vars.Header.ThemeName)) w.Write([]byte(tmpl_topic_vars.Header.Theme.Name))
w.Write(topic_24) w.Write(topic_24)
if tmpl_topic_vars.Topic.ContentLines <= 5 { if tmpl_topic_vars.Topic.ContentLines <= 5 {
w.Write(topic_25) w.Write(topic_25)
@ -223,7 +223,7 @@ if item.Avatar != "" {
w.Write(topic_65) w.Write(topic_65)
w.Write([]byte(item.Avatar)) w.Write([]byte(item.Avatar))
w.Write(topic_66) w.Write(topic_66)
w.Write([]byte(tmpl_topic_vars.Header.ThemeName)) w.Write([]byte(tmpl_topic_vars.Header.Theme.Name))
w.Write(topic_67) w.Write(topic_67)
if item.ContentLines <= 5 { if item.ContentLines <= 5 {
w.Write(topic_68) w.Write(topic_68)
@ -296,28 +296,37 @@ w.Write(topic_98)
w.Write(topic_99) w.Write(topic_99)
} }
w.Write(topic_100) w.Write(topic_100)
if tmpl_topic_vars.Header.Theme.AboutSegment {
w.Write(footer_0) w.Write(footer_0)
dispInt := tmpl_topic_vars.Header.Settings["about_segment_title"]
w.Write([]byte(dispInt.(string)))
w.Write(footer_1)
dispInt = tmpl_topic_vars.Header.Settings["about_segment_body"]
w.Write([]byte(dispInt.(string)))
w.Write(footer_2)
}
w.Write(footer_3)
if len(tmpl_topic_vars.Header.Themes) != 0 { if len(tmpl_topic_vars.Header.Themes) != 0 {
for _, item := range tmpl_topic_vars.Header.Themes { for _, item := range tmpl_topic_vars.Header.Themes {
if !item.HideFromThemes { if !item.HideFromThemes {
w.Write(footer_1)
w.Write([]byte(item.Name))
w.Write(footer_2)
if tmpl_topic_vars.Header.ThemeName == item.Name {
w.Write(footer_3)
}
w.Write(footer_4) w.Write(footer_4)
w.Write([]byte(item.FriendlyName)) w.Write([]byte(item.Name))
w.Write(footer_5) w.Write(footer_5)
} if tmpl_topic_vars.Header.Theme.Name == item.Name {
}
}
w.Write(footer_6) w.Write(footer_6)
if tmpl_topic_vars.Header.Widgets.RightSidebar != "" { }
w.Write(footer_7) w.Write(footer_7)
w.Write([]byte(string(tmpl_topic_vars.Header.Widgets.RightSidebar))) w.Write([]byte(item.FriendlyName))
w.Write(footer_8) w.Write(footer_8)
} }
}
}
w.Write(footer_9) w.Write(footer_9)
if tmpl_topic_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_10)
w.Write([]byte(string(tmpl_topic_vars.Header.Widgets.RightSidebar)))
w.Write(footer_11)
}
w.Write(footer_12)
return nil return nil
} }

View File

@ -3,9 +3,9 @@
// Code generated by Gosora. More below: // Code generated by Gosora. More below:
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */ /* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main package main
import "strconv"
import "net/http" import "net/http"
import "./common" import "./common"
import "strconv"
// nolint // nolint
func init() { func init() {
@ -22,7 +22,7 @@ w.Write([]byte(tmpl_topic_alt_vars.Title))
w.Write(header_1) w.Write(header_1)
w.Write([]byte(tmpl_topic_alt_vars.Header.Site.Name)) w.Write([]byte(tmpl_topic_alt_vars.Header.Site.Name))
w.Write(header_2) w.Write(header_2)
w.Write([]byte(tmpl_topic_alt_vars.Header.ThemeName)) w.Write([]byte(tmpl_topic_alt_vars.Header.Theme.Name))
w.Write(header_3) w.Write(header_3)
if len(tmpl_topic_alt_vars.Header.Stylesheets) != 0 { if len(tmpl_topic_alt_vars.Header.Stylesheets) != 0 {
for _, item := range tmpl_topic_alt_vars.Header.Stylesheets { for _, item := range tmpl_topic_alt_vars.Header.Stylesheets {
@ -302,28 +302,37 @@ w.Write(topic_alt_102)
w.Write(topic_alt_103) w.Write(topic_alt_103)
} }
w.Write(topic_alt_104) w.Write(topic_alt_104)
if tmpl_topic_alt_vars.Header.Theme.AboutSegment {
w.Write(footer_0) w.Write(footer_0)
dispInt := tmpl_topic_alt_vars.Header.Settings["about_segment_title"]
w.Write([]byte(dispInt.(string)))
w.Write(footer_1)
dispInt = tmpl_topic_alt_vars.Header.Settings["about_segment_body"]
w.Write([]byte(dispInt.(string)))
w.Write(footer_2)
}
w.Write(footer_3)
if len(tmpl_topic_alt_vars.Header.Themes) != 0 { if len(tmpl_topic_alt_vars.Header.Themes) != 0 {
for _, item := range tmpl_topic_alt_vars.Header.Themes { for _, item := range tmpl_topic_alt_vars.Header.Themes {
if !item.HideFromThemes { if !item.HideFromThemes {
w.Write(footer_1)
w.Write([]byte(item.Name))
w.Write(footer_2)
if tmpl_topic_alt_vars.Header.ThemeName == item.Name {
w.Write(footer_3)
}
w.Write(footer_4) w.Write(footer_4)
w.Write([]byte(item.FriendlyName)) w.Write([]byte(item.Name))
w.Write(footer_5) w.Write(footer_5)
} if tmpl_topic_alt_vars.Header.Theme.Name == item.Name {
}
}
w.Write(footer_6) w.Write(footer_6)
if tmpl_topic_alt_vars.Header.Widgets.RightSidebar != "" { }
w.Write(footer_7) w.Write(footer_7)
w.Write([]byte(string(tmpl_topic_alt_vars.Header.Widgets.RightSidebar))) w.Write([]byte(item.FriendlyName))
w.Write(footer_8) w.Write(footer_8)
} }
}
}
w.Write(footer_9) w.Write(footer_9)
if tmpl_topic_alt_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_10)
w.Write([]byte(string(tmpl_topic_alt_vars.Header.Widgets.RightSidebar)))
w.Write(footer_11)
}
w.Write(footer_12)
return nil return nil
} }

View File

@ -22,7 +22,7 @@ w.Write([]byte(tmpl_topics_vars.Title))
w.Write(header_1) w.Write(header_1)
w.Write([]byte(tmpl_topics_vars.Header.Site.Name)) w.Write([]byte(tmpl_topics_vars.Header.Site.Name))
w.Write(header_2) w.Write(header_2)
w.Write([]byte(tmpl_topics_vars.Header.ThemeName)) w.Write([]byte(tmpl_topics_vars.Header.Theme.Name))
w.Write(header_3) w.Write(header_3)
if len(tmpl_topics_vars.Header.Stylesheets) != 0 { if len(tmpl_topics_vars.Header.Stylesheets) != 0 {
for _, item := range tmpl_topics_vars.Header.Stylesheets { for _, item := range tmpl_topics_vars.Header.Stylesheets {
@ -199,28 +199,37 @@ w.Write(topics_55)
w.Write(topics_56) w.Write(topics_56)
} }
w.Write(topics_57) w.Write(topics_57)
if tmpl_topics_vars.Header.Theme.AboutSegment {
w.Write(footer_0) w.Write(footer_0)
dispInt := tmpl_topics_vars.Header.Settings["about_segment_title"]
w.Write([]byte(dispInt.(string)))
w.Write(footer_1)
dispInt = tmpl_topics_vars.Header.Settings["about_segment_body"]
w.Write([]byte(dispInt.(string)))
w.Write(footer_2)
}
w.Write(footer_3)
if len(tmpl_topics_vars.Header.Themes) != 0 { if len(tmpl_topics_vars.Header.Themes) != 0 {
for _, item := range tmpl_topics_vars.Header.Themes { for _, item := range tmpl_topics_vars.Header.Themes {
if !item.HideFromThemes { if !item.HideFromThemes {
w.Write(footer_1)
w.Write([]byte(item.Name))
w.Write(footer_2)
if tmpl_topics_vars.Header.ThemeName == item.Name {
w.Write(footer_3)
}
w.Write(footer_4) w.Write(footer_4)
w.Write([]byte(item.FriendlyName)) w.Write([]byte(item.Name))
w.Write(footer_5) w.Write(footer_5)
} if tmpl_topics_vars.Header.Theme.Name == item.Name {
}
}
w.Write(footer_6) w.Write(footer_6)
if tmpl_topics_vars.Header.Widgets.RightSidebar != "" { }
w.Write(footer_7) w.Write(footer_7)
w.Write([]byte(string(tmpl_topics_vars.Header.Widgets.RightSidebar))) w.Write([]byte(item.FriendlyName))
w.Write(footer_8) w.Write(footer_8)
} }
}
}
w.Write(footer_9) w.Write(footer_9)
if tmpl_topics_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_10)
w.Write([]byte(string(tmpl_topics_vars.Header.Widgets.RightSidebar)))
w.Write(footer_11)
}
w.Write(footer_12)
return nil return nil
} }

View File

@ -1,6 +1,8 @@
<nav class="colstack_left"> <nav class="colstack_left">
<div class="colstack_item colstack_head rowhead"> <div class="colstack_item colstack_head rowhead">
<div class="rowitem"><h1>My Account</h1></div> <div class="rowitem">
<a href="/user/edit/critical/"><h1>My Account</h1></a>
</div>
</div> </div>
<div class="colstack_item rowmenu"> <div class="colstack_item rowmenu">
<div class="rowitem passive"><a href="/user/edit/avatar/">Avatar</a></div> <div class="rowitem passive"><a href="/user/edit/avatar/">Avatar</a></div>

View File

@ -1,7 +1,7 @@
{{template "header.html" . }} {{template "header.html" . }}
<main class="colstack account"> <div class="colstack account">
{{template "account-menu.html" . }} {{template "account-menu.html" . }}
<div class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head rowhead"> <div class="colstack_item colstack_head rowhead">
<div class="rowitem"><h1>Edit Avatar</h1></div> <div class="rowitem"><h1>Edit Avatar</h1></div>
</div> </div>
@ -10,7 +10,7 @@
<div class="rowitem"><img src="{{.CurrentUser.Avatar}}" height="128px" max-width="128px" /></div> <div class="rowitem"><img src="{{.CurrentUser.Avatar}}" height="128px" max-width="128px" /></div>
</div> </div>
{{end}} {{end}}
<div class="colstack_item form_item"> <div class="colstack_item the_form">
<form action="/user/edit/avatar/submit/" method="post" enctype="multipart/form-data"> <form action="/user/edit/avatar/submit/" method="post" enctype="multipart/form-data">
<div class="formrow real_first_child"> <div class="formrow real_first_child">
<div class="formitem formlabel"><a>Upload Avatar</a></div> <div class="formitem formlabel"><a>Upload Avatar</a></div>
@ -21,6 +21,6 @@
</div> </div>
</form> </form>
</div> </div>
</div>
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,7 +1,7 @@
{{template "header.html" . }} {{template "header.html" . }}
<main class="colstack account"> <div class="colstack account">
{{template "account-menu.html" . }} {{template "account-menu.html" . }}
<div class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head rowhead"> <div class="colstack_item colstack_head rowhead">
<div class="rowitem"><h1>Emails</h1></div> <div class="rowitem"><h1>Emails</h1></div>
</div> </div>
@ -17,6 +17,6 @@
</div> </div>
{{end}} {{end}}
</div> </div>
</div>
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,11 +1,11 @@
{{template "header.html" . }} {{template "header.html" . }}
<main class="colstack account"> <div class="colstack account">
{{template "account-menu.html" . }} {{template "account-menu.html" . }}
<div class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head rowhead"> <div class="colstack_item colstack_head rowhead">
<div class="rowitem"><h1>Edit Username</h1></div> <div class="rowitem"><h1>Edit Username</h1></div>
</div> </div>
<div class="colstack_item form_item"> <div class="colstack_item the_form">
<form action="/user/edit/username/submit/" method="post"> <form action="/user/edit/username/submit/" method="post">
<div class="formrow real_first_child"> <div class="formrow real_first_child">
<div class="formitem formlabel"><a>Current Username</a></div> <div class="formitem formlabel"><a>Current Username</a></div>
@ -20,6 +20,6 @@
</div> </div>
</form> </form>
</div> </div>
</div>
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,11 +1,11 @@
{{template "header.html" . }} {{template "header.html" . }}
<main class="colstack account"> <div class="colstack account">
{{template "account-menu.html" . }} {{template "account-menu.html" . }}
<div class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head rowhead"> <div class="colstack_item colstack_head rowhead">
<div class="rowitem"><h1>Edit Password</h1></div> <div class="rowitem"><h1>Edit Password</h1></div>
</div> </div>
<div class="colstack_item form_item"> <div class="colstack_item the_form">
<form action="/user/edit/critical/submit/" method="post"> <form action="/user/edit/critical/submit/" method="post">
<div class="formrow real_first_child"> <div class="formrow real_first_child">
<div class="formitem formlabel"><a>Current Password</a></div> <div class="formitem formlabel"><a>Current Password</a></div>
@ -24,6 +24,6 @@
</div> </div>
</form> </form>
</div> </div>
</div>
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,10 +1,16 @@
{{if .Header.Theme.AboutSegment}}<div class="about">
<a id="aboutTitle">{{.Header.Settings.about_segment_title}}</a>
<span id="aboutDesc">{{.Header.Settings.about_segment_body}}</span>
</div>{{end}}
<div class="footer"> <div class="footer">
<div id="poweredBy">Powered by Gosora - <span>Made with love by Azareal</span></div> <div id="poweredBy">
<a id="poweredByName" href="https://github.com/Azareal/Gosora">Powered by Gosora</a><span id="poweredByDash"> - </span><span id="poweredByMaker">Made with love by Azareal</span>
</div>
<form action="/theme/" method="post"> <form action="/theme/" method="post">
<div id="themeSelector" style="float: right;"> <div id="themeSelector" style="float: right;">
<select id="themeSelectorSelect" name="themeSelector" aria-label="Change the site's appearance"> <select id="themeSelectorSelect" name="themeSelector" aria-label="Change the site's appearance">
{{range .Header.Themes}} {{range .Header.Themes}}
{{if not .HideFromThemes}}<option val="{{.Name}}"{{if eq $.Header.ThemeName .Name}} selected{{end}}>{{.FriendlyName}}</option>{{end}} {{if not .HideFromThemes}}<option val="{{.Name}}"{{if eq $.Header.Theme.Name .Name}} selected{{end}}>{{.FriendlyName}}</option>{{end}}
{{end}} {{end}}
</select> </select>
</div> </div>

View File

@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<title>{{.Title}} | {{.Header.Site.Name}}</title> <title>{{.Title}} | {{.Header.Site.Name}}</title>
<link href="/static/{{.Header.ThemeName}}/main.css" rel="stylesheet" type="text/css"> <link href="/static/{{.Header.Theme.Name}}/main.css" rel="stylesheet" type="text/css">
{{range .Header.Stylesheets}} {{range .Header.Stylesheets}}
<link href="/static/{{.}}" rel="stylesheet" type="text/css"> <link href="/static/{{.}}" rel="stylesheet" type="text/css">
{{end}} {{end}}

View File

@ -1,4 +1,5 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack">
<nav class="colstack_left" aria-label="The control panel menu"> <nav class="colstack_left" aria-label="The control panel menu">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><a href="/panel/logs/mod/">Logs</a></div> <div class="rowitem"><a href="/panel/logs/mod/">Logs</a></div>
@ -25,4 +26,5 @@
{{end}} {{end}}
</div> </div>
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,4 +1,5 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
@ -17,4 +18,5 @@
{{end}} {{end}}
</div> </div>
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,4 +1,5 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right"> <main id="panel_dashboard_right" class="colstack_right">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
@ -7,8 +8,9 @@
<div id="panel_dashboard" class="colstack_grid"> <div id="panel_dashboard" class="colstack_grid">
{{range .GridItems}} {{range .GridItems}}
<div id="{{.ID}}" class="grid_item {{.Class}}" title="{{.Note}}" style="{{if .TextColour}}color: {{.TextColour}};{{end}} <div id="{{.ID}}" class="grid_item {{.Class}}" title="{{.Note}}" style="{{if .TextColour}}color: {{.TextColour}};{{end}}
{{if .Background}}background-color: {{.Background}};{{end}}">{{.Body}}</div> {{if .Background}}background-color: {{.Background}};{{end}}"><span>{{.Body}}</span></div>
{{end}} {{end}}
</div> </div>
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,4 +1,5 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right"> <main id="panel_dashboard_right" class="colstack_right">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
@ -14,4 +15,5 @@
<div class="grid_item grid_stat">{{.DBAdapter}}</div> <div class="grid_item grid_stat">{{.DBAdapter}}</div>
</div> </div>
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,4 +1,6 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<script> <script>
var form_vars = {'perm_preset': ['can_moderate','can_post','read_only','no_access','default','custom']}; var form_vars = {'perm_preset': ['can_moderate','can_post','read_only','no_access','default','custom']};
@ -63,4 +65,5 @@ var form_vars = {'perm_preset': ['can_moderate','can_post','read_only','no_acces
{{end}} {{end}}
</div> </div>
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,4 +1,6 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<script>var form_vars = { <script>var form_vars = {
'forum_active': ['Hide','Show'], 'forum_active': ['Hide','Show'],
@ -72,4 +74,5 @@
</div> </div>
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,4 +1,5 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack">
<nav class="colstack_left" aria-label="The control panel menu"> <nav class="colstack_left" aria-label="The control panel menu">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><a href="/panel/groups/edit/{{.ID}}">Group Editor</a></div> <div class="rowitem"><a href="/panel/groups/edit/{{.ID}}">Group Editor</a></div>
@ -60,4 +61,5 @@
</div> </div>
</form> </form>
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,4 +1,5 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack">
<nav class="colstack_left" aria-label="The control panel menu"> <nav class="colstack_left" aria-label="The control panel menu">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><a href="/panel/groups/edit/{{.ID}}">Group Editor</a></div> <div class="rowitem"><a href="/panel/groups/edit/{{.ID}}">Group Editor</a></div>
@ -43,4 +44,5 @@
</form> </form>
</div> </div>
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,6 +1,7 @@
{{template "header.html" . }} {{template "header.html" . }}
{{template "panel-menu.html" . }} <div class="colstack">
{{template "panel-menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>Groups</h1></div> <div class="rowitem"><h1>Groups</h1></div>
@ -57,4 +58,6 @@
</form> </form>
</div> </div>
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,4 +1,6 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack">
<nav class="colstack_left" aria-label="The control panel menu"> <nav class="colstack_left" aria-label="The control panel menu">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><a href="/panel/logs/mod/">Logs</a></div> <div class="rowitem"><a href="/panel/logs/mod/">Logs</a></div>
@ -37,4 +39,6 @@
</div> </div>
{{end}} {{end}}
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,4 +1,6 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
@ -21,4 +23,6 @@
{{end}} {{end}}
</div> </div>
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,4 +1,6 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
@ -34,4 +36,6 @@
</form> </form>
</div> </div>
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,4 +1,6 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
@ -13,4 +15,6 @@
{{end}} {{end}}
</div> </div>
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,4 +1,6 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack">
<nav class="colstack_left" aria-label="The control panel menu"> <nav class="colstack_left" aria-label="The control panel menu">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><a href="/panel/themes/">Theme Manager</a></div> <div class="rowitem"><a href="/panel/themes/">Theme Manager</a></div>
@ -56,4 +58,6 @@
{{end}} {{end}}
</div> </div>
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,4 +1,6 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
@ -33,4 +35,6 @@
</form> </form>
</div> </div>
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,6 +1,7 @@
{{template "header.html" . }} {{template "header.html" . }}
{{template "panel-menu.html" . }} <div class="colstack">
{{template "panel-menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>Users</h1></div> <div class="rowitem"><h1>Users</h1></div>
@ -29,4 +30,6 @@
</div> </div>
{{end}} {{end}}
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,4 +1,6 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
@ -42,4 +44,6 @@
</form> </form>
</div> </div>
</main> </main>
</div>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,6 +1,6 @@
{{template "header.html" . }} {{template "header.html" . }}
<div id="profile_container"> <div id="profile_container" class="colstack">
<div id="profile_left_lane" class="colstack_left"> <div id="profile_left_lane" class="colstack_left">
<!--<header class="colstack_item colstack_head rowhead"> <!--<header class="colstack_item colstack_head rowhead">

View File

@ -23,7 +23,7 @@
</div> </div>
<article itemscope itemtype="http://schema.org/CreativeWork" class="rowblock post_container top_post" aria-label="The opening post for this topic"> <article itemscope itemtype="http://schema.org/CreativeWork" class="rowblock post_container top_post" aria-label="The opening post for this topic">
<div class="rowitem passive editable_parent post_item {{.Topic.ClassName}}" style="{{if .Topic.Avatar}}background-image: url({{.Topic.Avatar}}), url(/static/{{.Header.ThemeName}}/post-avatar-bg.jpg);background-position: 0px {{if le .Topic.ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;{{end}}"> <div class="rowitem passive editable_parent post_item {{.Topic.ClassName}}" style="{{if .Topic.Avatar}}background-image: url({{.Topic.Avatar}}), url(/static/{{.Header.Theme.Name}}/post-avatar-bg.jpg);background-position: 0px {{if le .Topic.ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;{{end}}">
<p class="hide_on_edit topic_content user_content" itemprop="text" style="margin:0;padding:0;">{{.Topic.ContentHTML}}</p> <p class="hide_on_edit topic_content user_content" itemprop="text" style="margin:0;padding:0;">{{.Topic.ContentHTML}}</p>
<textarea name="topic_content" class="show_on_edit topic_content_input">{{.Topic.Content}}</textarea> <textarea name="topic_content" class="show_on_edit topic_content_input">{{.Topic.Content}}</textarea>
@ -57,7 +57,7 @@
<span itemprop="text">{{.ActionType}}</span> <span itemprop="text">{{.ActionType}}</span>
</article> </article>
{{else}} {{else}}
<article itemscope itemtype="http://schema.org/CreativeWork" class="rowitem passive deletable_block editable_parent post_item {{.ClassName}}" style="{{if .Avatar}}background-image: url({{.Avatar}}), url(/static/{{$.Header.ThemeName}}/post-avatar-bg.jpg);background-position: 0px {{if le .ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;{{end}}"> <article itemscope itemtype="http://schema.org/CreativeWork" class="rowitem passive deletable_block editable_parent post_item {{.ClassName}}" style="{{if .Avatar}}background-image: url({{.Avatar}}), url(/static/{{$.Header.Theme.Name}}/post-avatar-bg.jpg);background-position: 0px {{if le .ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;{{end}}">
{{/** TODO: We might end up with <br>s in the inline editor, fix this **/}} {{/** TODO: We might end up with <br>s in the inline editor, fix this **/}}
<p class="editable_block user_content" itemprop="text" style="margin:0;padding:0;">{{.ContentHtml}}</p> <p class="editable_block user_content" itemprop="text" style="margin:0;padding:0;">{{.ContentHtml}}</p>

View File

@ -1,6 +1,6 @@
:root { :root {
--header-border-color: hsl(0,0%,85%); --header-border-color: hsl(0,0%,80%);
--element-border-color: hsl(0,0%,90%); --element-border-color: hsl(0,0%,85%);
--element-background-color: white; --element-background-color: white;
--replies-lang-string: " replies"; --replies-lang-string: " replies";
--topics-lang-string: " topics"; --topics-lang-string: " topics";
@ -45,8 +45,10 @@ a {
padding-top: 14px; padding-top: 14px;
display: flex; display: flex;
/*background-color: hsl(0,0%,97%);*/ /*background-color: hsl(0,0%,97%);*/
background-color: hsl(0,0%,98%);
padding-left: 0px; padding-left: 0px;
padding-right: 0px; padding-right: 0px;
padding-bottom: 0px;
} }
#main { #main {
@ -163,14 +165,22 @@ ul {
border-bottom: 2px solid var(--header-border-color); border-bottom: 2px solid var(--header-border-color);
background-color: var(--element-background-color); background-color: var(--element-background-color);
margin-left: 12px; margin-left: 12px;
}
.rowblock {
margin-right: 12px; margin-right: 12px;
} }
.colstack_right {
padding-right: 12px;
}
.rowhead, .opthead, .colstack_head { .rowhead, .opthead, .colstack_head {
padding: 13px; padding: 13px;
padding-top: 14px; padding-top: 14px;
padding-bottom: 14px; padding-bottom: 14px;
} }
.rowhead:not(:first-child), .opthead:not(:first-child), .colstack_head:not(:first-child) {
margin-top: 8px;
}
.rowhead h1, .opthead h1, .colstack_head h1 { .rowhead h1, .opthead h1, .colstack_head h1 {
font-size: 20px; font-size: 20px;
font-weight: normal; font-weight: normal;
@ -181,14 +191,18 @@ ul {
margin-block-end: 0; margin-block-end: 0;
display: inline-block; display: inline-block;
} }
.colstack_head a h1 {
font-size: 16px;
color: var(--primary-link-color);
}
.colstack { .colstack {
display: flex; display: flex;
} }
#main .colstack_left { .colstack:not(#profile_container) .colstack_left {
width: 300px; width: 300px;
} }
#main .colstack_right { .colstack:not(#profile_container) .colstack_right {
width: calc(90% - 300px); width: calc(90% - 300px);
} }
@ -412,8 +426,9 @@ label.uploadItem {
padding-left: 33px; padding-left: 33px;
} }
select, input, textarea { select, input, textarea, button {
border: 1px solid var(--header-border-color); border: 1px solid var(--header-border-color);
background: var(--element-background-color);
padding: 5px; padding: 5px;
color: hsl(0,0%,30%); color: hsl(0,0%,30%);
} }
@ -713,7 +728,7 @@ select, input, textarea {
margin-right: 10px; margin-right: 10px;
} }
#profile_container, #profile_left_pane .topBlock { #profile_left_pane .topBlock {
display: flex; display: flex;
} }
#profile_left_lane { #profile_left_lane {
@ -725,6 +740,7 @@ select, input, textarea {
padding-bottom: 18px; padding-bottom: 18px;
border: 1px solid var(--element-border-color); border: 1px solid var(--element-border-color);
border-bottom: 2px solid var(--element-border-color); border-bottom: 2px solid var(--element-border-color);
background-color: var(--element-background-color);
} }
#profile_left_pane .avatarRow { #profile_left_pane .avatarRow {
padding: 24px; padding: 24px;
@ -748,17 +764,21 @@ select, input, textarea {
.rowmenu .passive { .rowmenu .passive {
border: 1px solid var(--element-border-color); border: 1px solid var(--element-border-color);
border-bottom: 2px solid var(--element-border-color); border-bottom: 2px solid var(--element-border-color);
background-color: var(--element-background-color);
margin-top: 6px; margin-top: 6px;
padding: 12px; padding: 12px;
padding-top: 10px; padding-top: 10px;
padding-bottom: 10px; padding-bottom: 10px;
} }
.rowmenu { .colstack:not(#profile_container) .rowmenu {
padding-left: 12px; padding-left: 12px;
padding-right: 12px;
} }
.rowmenu .passive:hover { .colstack:not(#profile_container) .rowmenu .passive {
margin-left: 4px; margin-top: 0px;
border-bottom: none;
}
.colstack:not(#profile_container) .rowmenu .passive:last-child {
border-bottom: 2px solid var(--element-border-color);
} }
#profile_left_pane .passiveBlock .passive { #profile_left_pane .passiveBlock .passive {
padding-left: 12px; padding-left: 12px;
@ -771,50 +791,158 @@ select, input, textarea {
#profile_right_lane .colstack_item, .colstack_right .colstack_item { #profile_right_lane .colstack_item, .colstack_right .colstack_item {
border: 1px solid var(--element-border-color); border: 1px solid var(--element-border-color);
border-bottom: 2px solid var(--element-border-color); border-bottom: 2px solid var(--element-border-color);
}
#profile_right_lane .colstack_item, .colstack_right .colstack_item, .colstack_right .colstack_grid {
margin-left: 16px; margin-left: 16px;
} }
#profile_right_lane .topic_reply_form {
.form_item { width: auto;
display: flex;
} }
.form_item form, .colstack_item .formrow {
display: contents; .colstack_item .formrow {
display: flex;
} }
.colstack_right .formrow { .colstack_right .formrow {
padding-left: 16px; padding-left: 16px;
padding-right: 16px; padding-right: 16px;
padding-bottom: 4px; padding-bottom: 4px;
border-right: 1px solid var(--element-border-color);
} }
.colstack_right .formrow:first-child { .colstack_right .formrow:first-child {
padding-top: 16px; padding-top: 16px;
} }
.colstack_right .formrow .formlabel {
padding-top: 5px;
}
.colstack_right .formrow:last-child { .colstack_right .formrow:last-child {
padding-bottom: 16px; padding-bottom: 16px;
} }
.colstack_item:not(#profile_right_lane) .formrow .formlabel { .colstack_item:not(#profile_right_lane) .formrow .formlabel {
width: min-content; width: 40%;
margin-right: 12px; margin-right: 12px;
white-space: nowrap; white-space: nowrap;
} }
.formitem:only-child {
width: 100%;
}
.the_form .formitem:only-child button {
margin-left: auto;
margin-right: auto;
}
.quick_reply_form, .topic_reply_form, .the_form {
background: var(--element-background-color);
}
.formrow {
border-right: none !important;
}
.footer { .about, .footer {
border-top: 1px solid var(--element-border-color); border-top: 1px solid var(--element-border-color);
padding: 12px; padding: 12px;
padding-top: 10px; padding-top: 10px;
padding-bottom: 10px; padding-bottom: 10px;
margin-top: 14px;
margin-left: -8px; margin-left: -8px;
margin-right: -8px; margin-right: -8px;
background-color: var(--element-background-color);
display: flex;
} }
.footer #poweredBy { .about, #poweredBy {
font-size: 17px; font-size: 17px;
display: flex;
flex-direction: column;
} }
.footer #poweredBy span { .about {
margin-top: 14px;
}
#poweredBy {
margin-right: auto;
}
#poweredBy span {
font-size: 16px; font-size: 16px;
} }
#aboutTitle {
font-size: 18px;
margin: 8px;
}
#poweredByName {
font-size: 17px;
margin: 4px;
}
#aboutDesc {
margin-left: 8px;
margin-top: 8px;
width: 60%;
}
#aboutDesc p:last-child {
-webkit-margin-after: 8px;
}
#aboutDesc p:first-child {
-webkit-margin-before: 3px;
}
#poweredByDash, #poweredByMaker {
display: none;
}
#themeSelectorSelect {
padding: 3px;
margin-top: 2px;
}
.colstack_grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.grid_item {
background: var(--element-background-color);
border: 1px solid var(--element-border-color);
border-bottom: 2px solid var(--element-border-color);
margin: 8px;
padding: 16px;
padding-left: 0px;
display: flex;
padding-top: 0px;
padding-bottom: 0px;
padding-right: 10px;
}
.grid_item span {
margin-top: 16px;
margin-bottom: 16px;
margin-left: auto;
margin-right: auto;
text-align: center;
}
#dash-version:before, #dash-cpu:before, #dash-ram:before {
display: inline-block;
background: hsl(0,0%,98%);
font: normal normal normal 14px/1 FontAwesome;
font-size: 20px;
padding-left: 17px;
padding-top: 16px;
padding-right: 19px;
color: hsl(0,0%,20%);
border-bottom: 1px solid var(--element-border-color);
}
#dash-version:before {
content: "\f126";
}
#dash-cpu:before {
content: "\f2db";
}
#dash-ram:before {
content: "\f233";
}
@media(max-width: 670px) { @media(max-width: 670px) {
.topic_inner_right { .topic_inner_right {
display: none; display: none;
} }
} }
/*#dash-cpu:before {
content: "\f2db";
width: 30px;
margin-right: 8px;
display: inline-block;
padding-left: 20px;
background: hsl(0,0%,98%);
font: normal normal normal 14px/1 FontAwesome;
}*/

View File

@ -0,0 +1,6 @@
.about {
display: none;
}
.footer {
margin-top: 14px;
}

View File

@ -6,6 +6,7 @@
"URL": "github.com/Azareal/Gosora", "URL": "github.com/Azareal/Gosora",
"Tag": "WIP", "Tag": "WIP",
"Sidebars":"right", "Sidebars":"right",
"AboutSegment":true,
"Templates": [ "Templates": [
{ {
"Name": "topic", "Name": "topic",

View File

@ -352,11 +352,12 @@ AdminStatLoop:
} else { } else {
calcperc := int(cpuPerc[0]) / runtime.NumCPU() calcperc := int(cpuPerc[0]) / runtime.NumCPU()
cpustr = strconv.Itoa(calcperc) cpustr = strconv.Itoa(calcperc)
if calcperc < 30 { switch {
case calcperc < 30:
cpuColour = "stat_green" cpuColour = "stat_green"
} else if calcperc < 75 { case calcperc < 75:
cpuColour = "stat_orange" cpuColour = "stat_orange"
} else { default:
cpuColour = "stat_red" cpuColour = "stat_red"
} }
} }
@ -409,20 +410,20 @@ AdminStatLoop:
// nolint // nolint
if !noStatUpdates { if !noStatUpdates {
w.Write([]byte("set #dash-totonline " + strconv.Itoa(totonline) + totunit + " online\r")) w.Write([]byte("set #dash-totonline <span>" + strconv.Itoa(totonline) + totunit + " online</span>\r"))
w.Write([]byte("set #dash-gonline " + strconv.Itoa(gonline) + gunit + " guests online\r")) w.Write([]byte("set #dash-gonline <span>" + strconv.Itoa(gonline) + gunit + " guests online</span>\r"))
w.Write([]byte("set #dash-uonline " + strconv.Itoa(uonline) + uunit + " users online\r")) w.Write([]byte("set #dash-uonline <span>" + strconv.Itoa(uonline) + uunit + " users online</span>\r"))
w.Write([]byte("set-class #dash-totonline grid_item grid_stat " + onlineColour + "\r")) w.Write([]byte("set-class #dash-totonline grid_item grid_stat " + onlineColour + "\r"))
w.Write([]byte("set-class #dash-gonline grid_item grid_stat " + onlineGuestsColour + "\r")) w.Write([]byte("set-class #dash-gonline grid_item grid_stat " + onlineGuestsColour + "\r"))
w.Write([]byte("set-class #dash-uonline grid_item grid_stat " + onlineUsersColour + "\r")) w.Write([]byte("set-class #dash-uonline grid_item grid_stat " + onlineUsersColour + "\r"))
} }
w.Write([]byte("set #dash-cpu CPU: " + cpustr + "%\r")) w.Write([]byte("set #dash-cpu <span>CPU: " + cpustr + "%</span>\r"))
w.Write([]byte("set-class #dash-cpu grid_item grid_istat " + cpuColour + "\r")) w.Write([]byte("set-class #dash-cpu grid_item grid_istat " + cpuColour + "\r"))
if !noRAMUpdates { if !noRAMUpdates {
w.Write([]byte("set #dash-ram RAM: " + ramstr + "\r")) w.Write([]byte("set #dash-ram <span>RAM: " + ramstr + "</span>\r"))
w.Write([]byte("set-class #dash-ram grid_item grid_istat " + ramColour + "\r")) w.Write([]byte("set-class #dash-ram grid_item grid_istat " + ramColour + "\r"))
} }