Add a per-user theme switcher. The CSS might be slightly broken in the themes, that'll be fixed in a follow-up commit.

Added basic support for server sync.

Re-added a few missing defers.
Renamed TO-DO to TODO across the entire codebase.
Renamed StaticForumStore to MemoryForumStore.
The ForumStore is now built on a sync.Map with a view slice for generating /forums rather than a slice.
Renamed many more functions and variables to satisfy the linter.
increase_post_user_stats() and decrease_post_user_stats() are now methods on the User struct. We also fix a bug where they take the moderator's score rather than the target user's into account when recalculating their level after a post / topic is deleted.
Transitioned the topic list to CSS Grid for Tempra Simple, with a float fallback.
Cosmo and Cosmo Conflux are now hidden from the theme list.
Fixed more data races.
Added more debug data to the template compiler logs.
This commit is contained in:
Azareal 2017-09-10 17:57:22 +01:00
parent ba36814d8d
commit 91f70d2a4a
75 changed files with 1634 additions and 1114 deletions

View File

@ -127,6 +127,7 @@ func notifyWatchers(asid int64) {
log.Fatal(err.Error()) log.Fatal(err.Error())
return return
} }
defer rows.Close()
var uid int var uid int
var uids []int var uids []int
@ -143,7 +144,6 @@ func notifyWatchers(asid int64) {
log.Fatal(err.Error()) log.Fatal(err.Error())
return return
} }
_ = rows.Close()
var actorID, targetUserID, elementID int var actorID, targetUserID, elementID int
var event, elementType string var event, elementType string

View File

@ -109,6 +109,7 @@ func (auth *DefaultAuth) Logout(w http.ResponseWriter, _ int) {
http.SetCookie(w, &cookie) http.SetCookie(w, &cookie)
} }
// TODO: Set the cookie domain
func (auth *DefaultAuth) SetCookies(w http.ResponseWriter, uid int, session string) { func (auth *DefaultAuth) SetCookies(w http.ResponseWriter, uid int, session string) {
cookie := http.Cookie{Name: "uid", Value: strconv.Itoa(uid), Path: "/", MaxAge: year} cookie := http.Cookie{Name: "uid", Value: strconv.Itoa(uid), Path: "/", MaxAge: year}
http.SetCookie(w, &cookie) http.SetCookie(w, &cookie)

View File

@ -9,7 +9,7 @@ const CACHE_DYNAMIC int = 1
const CACHE_SQL int = 2 const CACHE_SQL int = 2
// ErrCacheDesync is thrown whenever a piece of data, for instance, a user is out of sync with the database. Currently unused. // ErrCacheDesync is thrown whenever a piece of data, for instance, a user is out of sync with the database. Currently unused.
var ErrCacheDesync = errors.New("The cache is out of sync with the database.") // TO-DO: A cross-server synchronisation mechanism var ErrCacheDesync = errors.New("The cache is out of sync with the database.") // TODO: A cross-server synchronisation mechanism
// ErrStoreCapacityOverflow is thrown whenever a datastore reaches it's maximum hard capacity. I'm not sure *if* this one is used, at the moment. Probably. // ErrStoreCapacityOverflow is thrown whenever a datastore reaches it's maximum hard capacity. I'm not sure *if* this one is used, at the moment. Probably.
var ErrStoreCapacityOverflow = errors.New("This datastore has reached it's maximum capacity.") var ErrStoreCapacityOverflow = errors.New("This datastore has reached it's maximum capacity.")

View File

@ -72,7 +72,7 @@ func initDatabase() (err error) {
GuestPerms = groups[6].Perms GuestPerms = groups[6].Perms
log.Print("Loading the forums.") log.Print("Loading the forums.")
fstore = NewStaticForumStore() fstore = NewMemoryForumStore()
err = fstore.LoadForums() err = fstore.LoadForums()
if err != nil { if err != nil {
return err return err

View File

@ -7,7 +7,7 @@ import "sync"
import "net/http" import "net/http"
import "runtime/debug" import "runtime/debug"
// TO-DO: Use the error_buffer variable to construct the system log in the Control Panel. Should we log errors caused by users too? Or just collect statistics on those or do nothing? Intercept recover()? Could we intercept the logger instead here? We might get too much information, if we intercept the logger, maybe make it part of the Debug page? // TODO: Use the error_buffer variable to construct the system log in the Control Panel. Should we log errors caused by users too? Or just collect statistics on those or do nothing? Intercept recover()? Could we intercept the logger instead here? We might get too much information, if we intercept the logger, maybe make it part of the Debug page?
var errorBufferMutex sync.RWMutex var errorBufferMutex sync.RWMutex
var errorBuffer []error var errorBuffer []error

View File

@ -15,7 +15,7 @@ var plugins = make(map[string]*Plugin)
var hooks = map[string][]func(interface{}) interface{}{ var hooks = map[string][]func(interface{}) interface{}{
"forums_frow_assign": nil, "forums_frow_assign": nil,
"topic_create_frow_assign": nil, "topic_create_frow_assign": nil,
"rrow_assign": nil, // TO-DO: Rename this hook to topic_rrow_assign "rrow_assign": nil, // TODO: Rename this hook to topic_rrow_assign
} }
// Hooks with a variable number of arguments // Hooks with a variable number of arguments

View File

@ -82,7 +82,7 @@ func addStaticFile(path string, prefix string) error {
func compressBytesGzip(in []byte) []byte { func compressBytesGzip(in []byte) []byte {
var buff bytes.Buffer var buff bytes.Buffer
gz := gzip.NewWriter(&buff) gz := gzip.NewWriter(&buff)
_, _ = gz.Write(in) // TO-DO: What if this errors? What circumstances could it error under? Should we add a second return value? _, _ = gz.Write(in) // TODO: What if this errors? What circumstances could it error under? Should we add a second return value?
_ = gz.Close() _ = gz.Close()
return buff.Bytes() return buff.Bytes()
} }

View File

@ -39,7 +39,7 @@ type ForumSimple struct {
Preset string Preset string
} }
func buildForumUrl(slug string, fid int) string { func buildForumURL(slug string, fid int) string {
if slug == "" { if slug == "" {
return "/forum/" + strconv.Itoa(fid) return "/forum/" + strconv.Itoa(fid)
} }

View File

@ -1,12 +1,14 @@
/* Work in progress. Check back later! */ /* Work in progress. Check back later! */
package main package main
import "log" import (
import "sync" "database/sql"
"log"
"sync"
"sync/atomic"
//import "sync/atomic" "./query_gen/lib"
import "database/sql" )
import "./query_gen/lib"
var forumUpdateMutex sync.Mutex var forumUpdateMutex sync.Mutex
var forumCreateMutex sync.Mutex var forumCreateMutex sync.Mutex
@ -24,7 +26,7 @@ type ForumStore interface {
Set(forum *Forum) error Set(forum *Forum) error
//Update(Forum) error //Update(Forum) error
//CascadeUpdate(Forum) error //CascadeUpdate(Forum) error
Delete(id int) error Delete(id int)
CascadeDelete(id int) error CascadeDelete(id int) error
IncrementTopicCount(id int) error IncrementTopicCount(id int) error
DecrementTopicCount(id int) error DecrementTopicCount(id int) error
@ -32,6 +34,8 @@ type ForumStore interface {
Exists(id int) bool Exists(id int) bool
GetAll() ([]*Forum, error) GetAll() ([]*Forum, error)
GetAllIDs() ([]int, error) GetAllIDs() ([]int, error)
GetAllVisible() ([]*Forum, error)
GetAllVisibleIDs() ([]int, error)
//GetChildren(parentID int, parentType string) ([]*Forum,error) //GetChildren(parentID int, parentType string) ([]*Forum,error)
//GetFirstChild(parentID int, parentType string) (*Forum,error) //GetFirstChild(parentID int, parentType string) (*Forum,error)
CreateForum(forumName string, forumDesc string, active bool, preset string) (int, error) CreateForum(forumName string, forumDesc string, active bool, preset string) (int, error)
@ -39,17 +43,20 @@ type ForumStore interface {
GetGlobalCount() int GetGlobalCount() int
} }
type StaticForumStore struct { type MemoryForumStore struct {
forums []*Forum // The IDs for a forum tend to be low and sequential for the most part, so we can get more performance out of using a slice instead of a map AND it has better concurrency //forums map[int]*Forum
forums sync.Map
forumView atomic.Value // []*Forum
//fids []int //fids []int
forumCapCount int forumCount int
get *sql.Stmt get *sql.Stmt
getAll *sql.Stmt getAll *sql.Stmt
forumCount *sql.Stmt delete *sql.Stmt
getForumCount *sql.Stmt
} }
func NewStaticForumStore() *StaticForumStore { func NewMemoryForumStore() *MemoryForumStore {
getStmt, err := qgen.Builder.SimpleSelect("forums", "name, desc, active, preset, parentID, parentType, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime", "fid = ?", "", "") getStmt, err := qgen.Builder.SimpleSelect("forums", "name, desc, active, preset, parentID, parentType, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime", "fid = ?", "", "")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -58,22 +65,36 @@ func NewStaticForumStore() *StaticForumStore {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
deleteStmt, err := qgen.Builder.SimpleUpdate("forums", "name= '', active = 0", "fid = ?")
if err != nil {
log.Fatal(err)
}
forumCountStmt, err := qgen.Builder.SimpleCount("forums", "name != ''", "") forumCountStmt, err := qgen.Builder.SimpleCount("forums", "name != ''", "")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
return &StaticForumStore{ return &MemoryForumStore{
get: getStmt, get: getStmt,
getAll: getAllStmt, getAll: getAllStmt,
forumCount: forumCountStmt, delete: deleteStmt,
getForumCount: forumCountStmt,
} }
} }
func (sfs *StaticForumStore) LoadForums() error { func (mfs *MemoryForumStore) LoadForums() error {
log.Print("Adding the uncategorised forum") log.Print("Adding the uncategorised forum")
var forums = []*Forum{ forumUpdateMutex.Lock()
&Forum{0, buildForumUrl(nameToSlug("Uncategorised"), 0), "Uncategorised", "", config.UncategorisedForumVisible, "all", 0, "", 0, "", "", 0, "", 0, ""}, defer forumUpdateMutex.Unlock()
var forumView []*Forum
addForum := func(forum *Forum) {
mfs.forums.Store(forum.ID, forum)
if forum.Active && forum.Name != "" {
forumView = append(forumView, forum)
} }
}
addForum(&Forum{0, buildForumURL(nameToSlug("Uncategorised"), 0), "Uncategorised", "", config.UncategorisedForumVisible, "all", 0, "", 0, "", "", 0, "", 0, ""})
rows, err := get_forums_stmt.Query() rows, err := get_forums_stmt.Query()
if err != nil { if err != nil {
@ -81,7 +102,7 @@ func (sfs *StaticForumStore) LoadForums() error {
} }
defer rows.Close() defer rows.Close()
var i = 1 var i = 0
for ; rows.Next(); i++ { for ; rows.Next(); i++ {
forum := Forum{ID: 0, Active: true, Preset: "all"} forum := Forum{ID: 0, Active: true, Preset: "all"}
err = rows.Scan(&forum.ID, &forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.ParentID, &forum.ParentType, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime) err = rows.Scan(&forum.ID, &forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.ParentID, &forum.ParentType, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime)
@ -89,12 +110,6 @@ func (sfs *StaticForumStore) LoadForums() error {
return err return err
} }
// Ugh, you really shouldn't physically delete these items, it makes a big mess of things
if forum.ID != i {
log.Print("Stop physically deleting forums. You are messing up the IDs. Use the Forum Manager or delete_forum() instead x.x")
sfs.fillForumIDGap(i, forum.ID)
}
if forum.Name == "" { if forum.Name == "" {
if dev.DebugMode { if dev.DebugMode {
log.Print("Adding a placeholder forum") log.Print("Adding a placeholder forum")
@ -103,132 +118,164 @@ func (sfs *StaticForumStore) LoadForums() error {
log.Print("Adding the " + forum.Name + " forum") log.Print("Adding the " + forum.Name + " forum")
} }
forum.Link = buildForumUrl(nameToSlug(forum.Name), forum.ID) forum.Link = buildForumURL(nameToSlug(forum.Name), forum.ID)
forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID) forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID)
forums = append(forums, &forum) addForum(&forum)
} }
err = rows.Err() mfs.forumCount = i
if err != nil { mfs.forumView.Store(forumView)
return err return rows.Err()
}
sfs.forums = forums
sfs.forumCapCount = i
return nil
} }
func (sfs *StaticForumStore) DirtyGet(id int) *Forum { // TODO: Hide social groups too
if !((id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name != "") { func (mfs *MemoryForumStore) rebuildView() {
var forumView []*Forum
mfs.forums.Range(func(_ interface{}, value interface{}) bool {
forum := value.(*Forum)
if forum.Active && forum.Name != "" {
forumView = append(forumView, forum)
}
return true
})
mfs.forumView.Store(forumView)
}
func (mfs *MemoryForumStore) DirtyGet(id int) *Forum {
fint, ok := mfs.forums.Load(id)
forum := fint.(*Forum)
if !ok || forum.Name == "" {
return &Forum{ID: -1, Name: ""} return &Forum{ID: -1, Name: ""}
} }
return sfs.forums[id] return forum
} }
func (sfs *StaticForumStore) Get(id int) (*Forum, error) { func (mfs *MemoryForumStore) Get(id int) (*Forum, error) {
if !((id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name != "") { fint, ok := mfs.forums.Load(id)
forum := fint.(*Forum)
if !ok || forum.Name == "" {
return nil, ErrNoRows return nil, ErrNoRows
} }
return sfs.forums[id], nil return forum, nil
} }
func (sfs *StaticForumStore) CascadeGet(id int) (*Forum, error) { func (mfs *MemoryForumStore) CascadeGet(id int) (*Forum, error) {
if !((id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name != "") { fint, ok := mfs.forums.Load(id)
return nil, ErrNoRows forum := fint.(*Forum)
if !ok || forum.Name == "" {
err := mfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime)
forum.Link = buildForumURL(nameToSlug(forum.Name), forum.ID)
forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID)
return forum, err
} }
return sfs.forums[id], nil return forum, nil
} }
func (sfs *StaticForumStore) CascadeGetCopy(id int) (forum Forum, err error) { func (mfs *MemoryForumStore) CascadeGetCopy(id int) (Forum, error) {
if !((id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name != "") { fint, ok := mfs.forums.Load(id)
return forum, ErrNoRows forum := fint.(*Forum)
if !ok || forum.Name == "" {
err := mfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime)
forum.Link = buildForumURL(nameToSlug(forum.Name), forum.ID)
forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID)
return *forum, err
} }
return *sfs.forums[id], nil return *forum, nil
} }
func (sfs *StaticForumStore) BypassGet(id int) (*Forum, error) { func (mfs *MemoryForumStore) BypassGet(id int) (*Forum, error) {
var forum = Forum{ID: id} var forum = Forum{ID: id}
err := sfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime) err := mfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime)
forum.Link = buildForumURL(nameToSlug(forum.Name), forum.ID)
forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID)
return &forum, err return &forum, err
} }
func (sfs *StaticForumStore) Load(id int) error { func (mfs *MemoryForumStore) Load(id int) error {
var forum = Forum{ID: id} var forum = Forum{ID: id}
err := sfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime) err := mfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime)
if err != nil { if err != nil {
return err return err
} }
sfs.Set(&forum) forum.Link = buildForumURL(nameToSlug(forum.Name), forum.ID)
forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID)
mfs.Set(&forum)
return nil return nil
} }
// TO-DO: Set should be able to add new indices not just replace existing ones for consistency with UserStore and TopicStore func (mfs *MemoryForumStore) Set(forum *Forum) error {
func (sfs *StaticForumStore) Set(forum *Forum) error { if !mfs.Exists(forum.ID) {
forumUpdateMutex.Lock()
if !sfs.Exists(forum.ID) {
forumUpdateMutex.Unlock()
return ErrNoRows return ErrNoRows
} }
sfs.forums[forum.ID] = forum mfs.forums.Store(forum.ID, forum)
forumUpdateMutex.Unlock() mfs.rebuildView()
return nil return nil
} }
func (sfs *StaticForumStore) GetAll() ([]*Forum, error) { func (mfs *MemoryForumStore) GetAll() (forumView []*Forum, err error) {
return sfs.forums, nil mfs.forums.Range(func(_ interface{}, value interface{}) bool {
forumView = append(forumView, value.(*Forum))
return true
})
return forumView, nil
} }
// TO-DO: Implement sub-forums. func (mfs *MemoryForumStore) GetAllIDs() (ids []int, err error) {
/*func (sfs *StaticForumStore) GetChildren(parentID int, parentType string) ([]*Forum,error) { mfs.forums.Range(func(_ interface{}, value interface{}) bool {
return nil, nil ids = append(ids, value.(*Forum).ID)
return true
})
return ids, nil
} }
func (sfs *StaticForumStore) GetFirstChild(parentID int, parentType string) (*Forum,error) {
return nil, nil
}*/
// We can cheat slightly, as the StaticForumStore has all the IDs under the cap ;) func (mfs *MemoryForumStore) GetAllVisible() ([]*Forum, error) {
// Should we cache this? Well, it's only really used for superadmins right now. return mfs.forumView.Load().([]*Forum), nil
func (sfs *StaticForumStore) GetAllIDs() ([]int, error) { }
var max = sfs.forumCapCount
var ids = make([]int, max) func (mfs *MemoryForumStore) GetAllVisibleIDs() ([]int, error) {
for i := 0; i < max; i++ { forumView := mfs.forumView.Load().([]*Forum)
ids[i] = i var ids = make([]int, len(forumView))
for i := 0; i < len(forumView); i++ {
ids[i] = forumView[i].ID
} }
return ids, nil return ids, nil
} }
func (sfs *StaticForumStore) Exists(id int) bool { // TODO: Implement sub-forums.
return (id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name != "" /*func (mfs *MemoryForumStore) GetChildren(parentID int, parentType string) ([]*Forum,error) {
return nil, nil
}
func (mfs *MemoryForumStore) GetFirstChild(parentID int, parentType string) (*Forum,error) {
return nil, nil
}*/
func (mfs *MemoryForumStore) Exists(id int) bool {
forum, ok := mfs.forums.Load(id)
return ok && forum.(*Forum).Name != ""
} }
func (sfs *StaticForumStore) Delete(id int) error { // TODO: Batch deletions with name blanking? Is this necessary?
func (mfs *MemoryForumStore) Delete(id int) {
mfs.forums.Delete(id)
mfs.rebuildView()
}
func (mfs *MemoryForumStore) CascadeDelete(id int) error {
forumUpdateMutex.Lock() forumUpdateMutex.Lock()
if !sfs.Exists(id) { defer forumUpdateMutex.Unlock()
forumUpdateMutex.Unlock() _, err := mfs.delete.Exec(id)
return nil
}
sfs.forums[id].Name = ""
forumUpdateMutex.Unlock()
return nil
}
func (sfs *StaticForumStore) CascadeDelete(id int) error {
forum, err := sfs.CascadeGet(id)
if err != nil { if err != nil {
return err return err
} }
mfs.Delete(id)
forumUpdateMutex.Lock()
_, err = delete_forum_stmt.Exec(id)
if err != nil {
return err
}
forum.Name = ""
forumUpdateMutex.Unlock()
return nil return nil
} }
func (sfs *StaticForumStore) IncrementTopicCount(id int) error { func (mfs *MemoryForumStore) IncrementTopicCount(id int) error {
forum, err := sfs.CascadeGet(id) forum, err := mfs.CascadeGet(id)
if err != nil { if err != nil {
return err return err
} }
@ -240,8 +287,8 @@ func (sfs *StaticForumStore) IncrementTopicCount(id int) error {
return nil return nil
} }
func (sfs *StaticForumStore) DecrementTopicCount(id int) error { func (mfs *MemoryForumStore) DecrementTopicCount(id int) error {
forum, err := sfs.CascadeGet(id) forum, err := mfs.CascadeGet(id)
if err != nil { if err != nil {
return err return err
} }
@ -253,19 +300,19 @@ func (sfs *StaticForumStore) DecrementTopicCount(id int) error {
return nil return nil
} }
// TO-DO: Have a pointer to the last topic rather than storing it on the forum itself // TODO: Have a pointer to the last topic rather than storing it on the forum itself
func (sfs *StaticForumStore) UpdateLastTopic(topic_name string, tid int, username string, uid int, time string, fid int) error { func (mfs *MemoryForumStore) UpdateLastTopic(topicName string, tid int, username string, uid int, time string, fid int) error {
forum, err := sfs.CascadeGet(fid) forum, err := mfs.CascadeGet(fid)
if err != nil { if err != nil {
return err return err
} }
_, err = update_forum_cache_stmt.Exec(topic_name, tid, username, uid, fid) _, err = update_forum_cache_stmt.Exec(topicName, tid, username, uid, fid)
if err != nil { if err != nil {
return err return err
} }
forum.LastTopic = topic_name forum.LastTopic = topicName
forum.LastTopicID = tid forum.LastTopicID = tid
forum.LastReplyer = username forum.LastReplyer = username
forum.LastReplyerID = uid forum.LastReplyerID = uid
@ -274,30 +321,7 @@ func (sfs *StaticForumStore) UpdateLastTopic(topic_name string, tid int, usernam
return nil return nil
} }
func (sfs *StaticForumStore) CreateForum(forumName string, forumDesc string, active bool, preset string) (int, error) { func (mfs *MemoryForumStore) CreateForum(forumName string, forumDesc string, active bool, preset string) (int, error) {
var fid int
err := forum_entry_exists_stmt.QueryRow().Scan(&fid)
if err != nil && err != ErrNoRows {
return 0, err
}
if err != ErrNoRows {
forumUpdateMutex.Lock()
_, err = update_forum_stmt.Exec(forumName, forumDesc, active, preset, fid)
if err != nil {
return fid, err
}
forum, err := sfs.Get(fid)
if err != nil {
return 0, ErrCacheDesync
}
forum.Name = forumName
forum.Desc = forumDesc
forum.Active = active
forum.Preset = preset
forumUpdateMutex.Unlock()
return fid, nil
}
forumCreateMutex.Lock() forumCreateMutex.Lock()
res, err := create_forum_stmt.Exec(forumName, forumDesc, active, preset) res, err := create_forum_stmt.Exec(forumName, forumDesc, active, preset)
if err != nil { if err != nil {
@ -308,34 +332,29 @@ func (sfs *StaticForumStore) CreateForum(forumName string, forumDesc string, act
if err != nil { if err != nil {
return 0, err return 0, err
} }
fid = int(fid64) fid := int(fid64)
sfs.forums = append(sfs.forums, &Forum{fid, buildForumUrl(nameToSlug(forumName), fid), forumName, forumDesc, active, preset, 0, "", 0, "", "", 0, "", 0, ""}) mfs.forums.Store(fid, &Forum{fid, buildForumURL(nameToSlug(forumName), fid), forumName, forumDesc, active, preset, 0, "", 0, "", "", 0, "", 0, ""})
sfs.forumCapCount++ mfs.forumCount++
// TO-DO: Add a GroupStore. How would it interact with the ForumStore? // TODO: Add a GroupStore. How would it interact with the ForumStore?
permmapToQuery(presetToPermmap(preset), fid) permmapToQuery(presetToPermmap(preset), fid)
forumCreateMutex.Unlock() forumCreateMutex.Unlock()
if active {
mfs.rebuildView()
}
return fid, nil return fid, nil
} }
func (sfs *StaticForumStore) fillForumIDGap(biggerID int, smallerID int) { // TODO: Get the total count of forums in the forum store minus the blanked forums rather than doing a heavy query for this?
dummy := Forum{ID: 0, Name: "", Active: false, Preset: "all"}
for i := smallerID; i > biggerID; i++ {
sfs.forums = append(sfs.forums, &dummy)
}
}
// TO-DO: Get the total count of forums in the forum store minus the blanked forums rather than doing a heavy query for this?
// GetGlobalCount returns the total number of forums // GetGlobalCount returns the total number of forums
func (sfs *StaticForumStore) GetGlobalCount() (fcount int) { func (mfs *MemoryForumStore) GetGlobalCount() (fcount int) {
err := sfs.forumCount.QueryRow().Scan(&fcount) err := mfs.getForumCount.QueryRow().Scan(&fcount)
if err != nil { if err != nil {
LogError(err) LogError(err)
} }
return fcount return fcount
} }
// TO-DO: Work on MapForumStore // TODO: Work on SqlForumStore
// TO-DO: Work on SqlForumStore

View File

@ -43,6 +43,7 @@ var forum_entry_exists_stmt *sql.Stmt
var group_entry_exists_stmt *sql.Stmt var group_entry_exists_stmt *sql.Stmt
var get_forum_topics_offset_stmt *sql.Stmt var get_forum_topics_offset_stmt *sql.Stmt
var get_expired_scheduled_groups_stmt *sql.Stmt var get_expired_scheduled_groups_stmt *sql.Stmt
var get_sync_stmt *sql.Stmt
var get_topic_replies_offset_stmt *sql.Stmt var get_topic_replies_offset_stmt *sql.Stmt
var get_topic_list_stmt *sql.Stmt var get_topic_list_stmt *sql.Stmt
var get_topic_user_stmt *sql.Stmt var get_topic_user_stmt *sql.Stmt
@ -96,7 +97,6 @@ var increment_user_bigposts_stmt *sql.Stmt
var increment_user_megaposts_stmt *sql.Stmt var increment_user_megaposts_stmt *sql.Stmt
var increment_user_topics_stmt *sql.Stmt var increment_user_topics_stmt *sql.Stmt
var edit_profile_reply_stmt *sql.Stmt var edit_profile_reply_stmt *sql.Stmt
var delete_forum_stmt *sql.Stmt
var update_forum_stmt *sql.Stmt var update_forum_stmt *sql.Stmt
var update_setting_stmt *sql.Stmt var update_setting_stmt *sql.Stmt
var update_plugin_stmt *sql.Stmt var update_plugin_stmt *sql.Stmt
@ -110,6 +110,7 @@ var update_email_stmt *sql.Stmt
var verify_email_stmt *sql.Stmt var verify_email_stmt *sql.Stmt
var set_temp_group_stmt *sql.Stmt var set_temp_group_stmt *sql.Stmt
var update_word_filter_stmt *sql.Stmt var update_word_filter_stmt *sql.Stmt
var bump_sync_stmt *sql.Stmt
var delete_reply_stmt *sql.Stmt var delete_reply_stmt *sql.Stmt
var delete_topic_stmt *sql.Stmt var delete_topic_stmt *sql.Stmt
var delete_profile_reply_stmt *sql.Stmt var delete_profile_reply_stmt *sql.Stmt
@ -340,6 +341,12 @@ func _gen_mysql() (err error) {
return err return err
} }
log.Print("Preparing get_sync statement.")
get_sync_stmt, err = db.Prepare("SELECT `last_update` FROM `sync`")
if err != nil {
return err
}
log.Print("Preparing get_topic_replies_offset statement.") log.Print("Preparing get_topic_replies_offset statement.")
get_topic_replies_offset_stmt, err = db.Prepare("SELECT `replies`.`rid`,`replies`.`content`,`replies`.`createdBy`,`replies`.`createdAt`,`replies`.`lastEdit`,`replies`.`lastEditBy`,`users`.`avatar`,`users`.`name`,`users`.`group`,`users`.`url_prefix`,`users`.`url_name`,`users`.`level`,`replies`.`ipaddress`,`replies`.`likeCount`,`replies`.`actionType` FROM `replies` LEFT JOIN `users` ON `replies`.`createdBy` = `users`.`uid` WHERE `tid` = ? LIMIT ?,?") get_topic_replies_offset_stmt, err = db.Prepare("SELECT `replies`.`rid`,`replies`.`content`,`replies`.`createdBy`,`replies`.`createdAt`,`replies`.`lastEdit`,`replies`.`lastEditBy`,`users`.`avatar`,`users`.`name`,`users`.`group`,`users`.`url_prefix`,`users`.`url_name`,`users`.`level`,`replies`.`ipaddress`,`replies`.`likeCount`,`replies`.`actionType` FROM `replies` LEFT JOIN `users` ON `replies`.`createdBy` = `users`.`uid` WHERE `tid` = ? LIMIT ?,?")
if err != nil { if err != nil {
@ -658,12 +665,6 @@ func _gen_mysql() (err error) {
return err return err
} }
log.Print("Preparing delete_forum statement.")
delete_forum_stmt, err = db.Prepare("UPDATE `forums` SET `name` = '',`active` = 0 WHERE `fid` = ?")
if err != nil {
return err
}
log.Print("Preparing update_forum statement.") log.Print("Preparing update_forum statement.")
update_forum_stmt, err = db.Prepare("UPDATE `forums` SET `name` = ?,`desc` = ?,`active` = ?,`preset` = ? WHERE `fid` = ?") update_forum_stmt, err = db.Prepare("UPDATE `forums` SET `name` = ?,`desc` = ?,`active` = ?,`preset` = ? WHERE `fid` = ?")
if err != nil { if err != nil {
@ -742,6 +743,12 @@ func _gen_mysql() (err error) {
return err return err
} }
log.Print("Preparing bump_sync statement.")
bump_sync_stmt, err = db.Prepare("UPDATE `sync` SET `last_update` = UTC_TIMESTAMP()")
if err != nil {
return err
}
log.Print("Preparing delete_reply statement.") log.Print("Preparing delete_reply statement.")
delete_reply_stmt, err = db.Prepare("DELETE FROM `replies` WHERE `rid` = ?") delete_reply_stmt, err = db.Prepare("DELETE FROM `replies` WHERE `rid` = ?")
if err != nil { if err != nil {

View File

@ -32,7 +32,6 @@ var increment_user_bigposts_stmt *sql.Stmt
var increment_user_megaposts_stmt *sql.Stmt var increment_user_megaposts_stmt *sql.Stmt
var increment_user_topics_stmt *sql.Stmt var increment_user_topics_stmt *sql.Stmt
var edit_profile_reply_stmt *sql.Stmt var edit_profile_reply_stmt *sql.Stmt
var delete_forum_stmt *sql.Stmt
var update_forum_stmt *sql.Stmt var update_forum_stmt *sql.Stmt
var update_setting_stmt *sql.Stmt var update_setting_stmt *sql.Stmt
var update_plugin_stmt *sql.Stmt var update_plugin_stmt *sql.Stmt
@ -46,6 +45,7 @@ var update_email_stmt *sql.Stmt
var verify_email_stmt *sql.Stmt var verify_email_stmt *sql.Stmt
var set_temp_group_stmt *sql.Stmt var set_temp_group_stmt *sql.Stmt
var update_word_filter_stmt *sql.Stmt var update_word_filter_stmt *sql.Stmt
var bump_sync_stmt *sql.Stmt
// nolint // nolint
func _gen_pgsql() (err error) { func _gen_pgsql() (err error) {
@ -203,12 +203,6 @@ func _gen_pgsql() (err error) {
return err return err
} }
log.Print("Preparing delete_forum statement.")
delete_forum_stmt, err = db.Prepare("UPDATE `forums` SET `name` = '',`active` = 0 WHERE `fid` = ?")
if err != nil {
return err
}
log.Print("Preparing update_forum statement.") log.Print("Preparing update_forum statement.")
update_forum_stmt, err = db.Prepare("UPDATE `forums` SET `name` = ?,`desc` = ?,`active` = ?,`preset` = ? WHERE `fid` = ?") update_forum_stmt, err = db.Prepare("UPDATE `forums` SET `name` = ?,`desc` = ?,`active` = ?,`preset` = ? WHERE `fid` = ?")
if err != nil { if err != nil {
@ -287,5 +281,11 @@ func _gen_pgsql() (err error) {
return err return err
} }
log.Print("Preparing bump_sync statement.")
bump_sync_stmt, err = db.Prepare("UPDATE `sync` SET `last_update` = LOCALTIMESTAMP()")
if err != nil {
return err
}
return nil return nil
} }

View File

@ -68,6 +68,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
log.Print("prefix: ", prefix) log.Print("prefix: ", prefix)
log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.Path: ", req.URL.Path)
log.Print("extra_data: ", extra_data) log.Print("extra_data: ", extra_data)
log.Print("req.Referer(): ", req.Referer())
} }
if prefix == "/static" { if prefix == "/static" {
@ -103,6 +104,9 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
case "/forum": case "/forum":
route_forum(w,req,user,extra_data) route_forum(w,req,user,extra_data)
return return
case "/theme":
route_change_theme(w,req,user)
return
case "/report": case "/report":
switch(req.URL.Path) { switch(req.URL.Path) {
case "/report/submit/": case "/report/submit/":
@ -230,7 +234,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
case "": case "":
// Stop the favicons, robots.txt file, etc. resolving to the topics list // Stop the favicons, robots.txt file, etc. resolving to the topics list
// TO-DO: Add support for favicons and robots.txt files // TODO: Add support for favicons and robots.txt files
switch(extra_data) { switch(extra_data) {
case "robots.txt": case "robots.txt":
route_robots_txt(w,req) route_robots_txt(w,req)

View File

@ -56,8 +56,8 @@ func gloinit() error {
users = NewMemoryUserStore(config.UserCacheCapacity) users = NewMemoryUserStore(config.UserCacheCapacity)
topics = NewMemoryTopicStore(config.TopicCacheCapacity) topics = NewMemoryTopicStore(config.TopicCacheCapacity)
} else { } else {
users = NewSqlUserStore() users = NewSQLUserStore()
topics = NewSqlTopicStore() topics = NewSQLTopicStore()
} }
log.Print("Loading the static files.") log.Print("Loading the static files.")
@ -67,7 +67,7 @@ func gloinit() error {
} }
auth = NewDefaultAuth() auth = NewDefaultAuth()
err = initWordFilters() err = LoadWordFilters()
if err != nil { if err != nil {
return err return err
} }
@ -84,7 +84,7 @@ func init() {
} }
} }
// TO-DO: Swap out LocalError for a panic for this? // TODO: Swap out LocalError for a panic for this?
func BenchmarkTopicAdminRouteParallel(b *testing.B) { func BenchmarkTopicAdminRouteParallel(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
if !gloinited { if !gloinited {
@ -143,7 +143,7 @@ func BenchmarkTopicGuestRouteParallel(b *testing.B) {
}) })
} }
// TO-DO: Make these routes compatible with the changes to the router // TODO: Make these routes compatible with the changes to the router
/* /*
func BenchmarkForumsAdminRouteParallel(b *testing.B) { func BenchmarkForumsAdminRouteParallel(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
@ -1091,7 +1091,7 @@ func TestLevels(t *testing.T) {
} }
} }
// TO-DO: Make this compatible with the changes to the router // TODO: Make this compatible with the changes to the router
/* /*
func TestStaticRoute(t *testing.T) { func TestStaticRoute(t *testing.T) {
if !gloinited { if !gloinited {
@ -1166,7 +1166,7 @@ func TestStaticRoute(t *testing.T) {
t.Print("No problems found in the topic-guest route!") t.Print("No problems found in the topic-guest route!")
}*/ }*/
// TO-DO: Make these routes compatible with the changes to the router // TODO: Make these routes compatible with the changes to the router
/* /*
func TestForumsAdminRoute(t *testing.T) { func TestForumsAdminRoute(t *testing.T) {
if !gloinited { if !gloinited {

View File

@ -31,12 +31,12 @@ func _initPgsql() (err error) {
} }
fmt.Println("Successfully connected to the database") fmt.Println("Successfully connected to the database")
// TO-DO: Create the database, if it doesn't exist // TODO: Create the database, if it doesn't exist
return nil return nil
} }
func _pgEscapeBit(bit string) string { func _pgEscapeBit(bit string) string {
// TO-DO: Write a custom parser, so that backslashes work properly in the sql.Open string. Do something similar for the database driver, if possible? // TODO: Write a custom parser, so that backslashes work properly in the sql.Open string. Do something similar for the database driver, if possible?
return strings.Replace(bit, "'", "\\'", -1) return strings.Replace(bit, "'", "\\'", -1)
} }

48
main.go
View File

@ -52,14 +52,14 @@ func init() {
wordFilterBox.Store(WordFilterBox(make(map[int]WordFilter))) wordFilterBox.Store(WordFilterBox(make(map[int]WordFilter)))
} }
func initWordFilters() error { func LoadWordFilters() error {
rows, err := get_word_filters_stmt.Query() rows, err := get_word_filters_stmt.Query()
if err != nil { if err != nil {
return err return err
} }
defer rows.Close() defer rows.Close()
var wordFilters = wordFilterBox.Load().(WordFilterBox) var wordFilters = WordFilterBox(make(map[int]WordFilter))
var wfid int var wfid int
var find string var find string
var replacement string var replacement string
@ -92,8 +92,8 @@ func processConfig() {
} }
func main() { func main() {
// TO-DO: Have a file for each run with the time/date the server started as the file name? // TODO: Have a file for each run with the time/date the server started as the file name?
// TO-DO: Log panics with recover() // TODO: Log panics with recover()
f, err := os.OpenFile("./operations.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755) f, err := os.OpenFile("./operations.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -137,8 +137,8 @@ func main() {
users = NewMemoryUserStore(config.UserCacheCapacity) users = NewMemoryUserStore(config.UserCacheCapacity)
topics = NewMemoryTopicStore(config.TopicCacheCapacity) topics = NewMemoryTopicStore(config.TopicCacheCapacity)
} else { } else {
users = NewSqlUserStore() users = NewSQLUserStore()
topics = NewSqlTopicStore() topics = NewSQLTopicStore()
} }
log.Print("Loading the static files.") log.Print("Loading the static files.")
@ -156,7 +156,7 @@ func main() {
log.Print("Initialising the authentication system") log.Print("Initialising the authentication system")
auth = NewDefaultAuth() auth = NewDefaultAuth()
err = initWordFilters() err = LoadWordFilters()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -174,16 +174,23 @@ func main() {
if err != nil { if err != nil {
LogError(err) LogError(err)
} }
// TO-DO: Handle delayed moderation tasks
// TO-DO: Handle the daily clean-up. Move this to a 24 hour task? // TODO: Handle delayed moderation tasks
// TO-DO: Sync with the database, if there are any changes // TODO: Handle the daily clean-up. Move this to a 24 hour task?
// TO-DO: Manage the TopicStore, UserStore, and ForumStore
// TO-DO: Alert the admin, if CPU usage, RAM usage, or the number of posts in the past second are too high // Sync with the database, if there are any changes
// TO-DO: Clean-up alerts with no unread matches which are over two weeks old. Move this to a 24 hour task? err = handleServerSync()
if err != nil {
LogError(err)
}
// TODO: Manage the TopicStore, UserStore, and ForumStore
// TODO: Alert the admin, if CPU usage, RAM usage, or the number of posts in the past second are too high
// TODO: Clean-up alerts with no unread matches which are over two weeks old. Move this to a 24 hour task?
case <-fifteenMinuteTicker.C: case <-fifteenMinuteTicker.C:
// TO-DO: Automatically lock topics, if they're really old, and the associated setting is enabled. // TODO: Automatically lock topics, if they're really old, and the associated setting is enabled.
// TO-DO: Publish scheduled posts. // TODO: Publish scheduled posts.
// TO-DO: Delete the empty users_groups_scheduler entries // TODO: Delete the empty users_groups_scheduler entries
} }
} }
}() }()
@ -286,12 +293,13 @@ func main() {
// pprof.StopCPUProfile() // pprof.StopCPUProfile()
//} //}
// TO-DO: Let users run *both* HTTP and HTTPS // TODO: Let users run *both* HTTP and HTTPS
log.Print("Initialising the HTTP server") log.Print("Initialising the HTTP server")
if !site.EnableSsl { if !site.EnableSsl {
if site.Port == "" { if site.Port == "" {
site.Port = "80" site.Port = "80"
} }
log.Print("Listening on port " + site.Port)
err = http.ListenAndServe(":"+site.Port, router) err = http.ListenAndServe(":"+site.Port, router)
} else { } else {
if site.Port == "" { if site.Port == "" {
@ -299,14 +307,16 @@ func main() {
} }
if site.Port == "80" || site.Port == "443" { if site.Port == "80" || site.Port == "443" {
// We should also run the server on port 80 // We should also run the server on port 80
// TO-DO: Redirect to port 443 // TODO: Redirect to port 443
go func() { go func() {
err = http.ListenAndServe(":80", &HttpsRedirect{}) log.Print("Listening on port 80")
err = http.ListenAndServe(":80", &HTTPSRedirect{})
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
}() }()
} }
log.Print("Listening on port " + site.Port)
err = http.ListenAndServeTLS(":"+site.Port, config.SslFullchain, config.SslPrivkey, router) err = http.ListenAndServeTLS(":"+site.Port, config.SslFullchain, config.SslPrivkey, router)
} }

View File

@ -3,7 +3,7 @@ package main
import "strconv" import "strconv"
import "testing" import "testing"
// TO-DO: Generate a test database to work with rather than a live one // TODO: Generate a test database to work with rather than a live one
func TestUserStore(t *testing.T) { func TestUserStore(t *testing.T) {
if !gloinited { if !gloinited {
err := gloinit() err := gloinit()
@ -18,14 +18,14 @@ func TestUserStore(t *testing.T) {
var user *User var user *User
var err error var err error
user, err = users.CascadeGet(-1) _, err = users.CascadeGet(-1)
if err == nil { if err == nil {
t.Error("UID #-1 shouldn't exist") t.Error("UID #-1 shouldn't exist")
} else if err != ErrNoRows { } else if err != ErrNoRows {
t.Fatal(err) t.Fatal(err)
} }
user, err = users.CascadeGet(0) _, err = users.CascadeGet(0)
if err == nil { if err == nil {
t.Error("UID #0 shouldn't exist") t.Error("UID #0 shouldn't exist")
} else if err != ErrNoRows { } else if err != ErrNoRows {
@ -43,7 +43,7 @@ func TestUserStore(t *testing.T) {
t.Error("user.ID does not match the requested UID. Got '" + strconv.Itoa(user.ID) + "' instead.") t.Error("user.ID does not match the requested UID. Got '" + strconv.Itoa(user.ID) + "' instead.")
} }
// TO-DO: 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]*User var userList map[int]*User
_, err = users.BulkCascadeGetMap([]int{-1}) _, err = users.BulkCascadeGetMap([]int{-1})
if err == nil { if err == nil {
@ -90,7 +90,7 @@ func TestForumStore(t *testing.T) {
var forum *Forum var forum *Forum
var err error var err error
forum, err = fstore.CascadeGet(-1) _, err = fstore.CascadeGet(-1)
if err == nil { if err == nil {
t.Error("FID #-1 shouldn't exist") t.Error("FID #-1 shouldn't exist")
} else if err != ErrNoRows { } else if err != ErrNoRows {
@ -131,6 +131,8 @@ func TestForumStore(t *testing.T) {
} else if err != nil { } else if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_ = forum
} }
func TestSlugs(t *testing.T) { func TestSlugs(t *testing.T) {

View File

@ -10,6 +10,8 @@ import (
"time" "time"
) )
// 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_socialgroups
func route_edit_topic(w http.ResponseWriter, r *http.Request, user User) { func route_edit_topic(w http.ResponseWriter, r *http.Request, user User) {
//log.Print("in route_edit_topic") //log.Print("in route_edit_topic")
err := r.ParseForm() err := r.ParseForm()
@ -35,7 +37,7 @@ func route_edit_topic(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
// TO-DO: Add hooks to make use of headerLite // TODO: Add hooks to make use of headerLite
_, ok := SimpleForumSessionCheck(w, r, &user, oldTopic.ParentID) _, ok := SimpleForumSessionCheck(w, r, &user, oldTopic.ParentID)
if !ok { if !ok {
return return
@ -108,6 +110,7 @@ func route_edit_topic(w http.ResponseWriter, r *http.Request, user User) {
} }
} }
// TODO: Disable stat updates in posts handled by plugin_socialgroups
func route_delete_topic(w http.ResponseWriter, r *http.Request, user User) { func route_delete_topic(w http.ResponseWriter, r *http.Request, user User) {
tid, err := strconv.Atoi(r.URL.Path[len("/topic/delete/submit/"):]) tid, err := strconv.Atoi(r.URL.Path[len("/topic/delete/submit/"):])
if err != nil { if err != nil {
@ -124,7 +127,7 @@ func route_delete_topic(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
// TO-DO: Add hooks to make use of headerLite // TODO: Add hooks to make use of headerLite
_, ok := SimpleForumSessionCheck(w, r, &user, topic.ParentID) _, ok := SimpleForumSessionCheck(w, r, &user, topic.ParentID)
if !ok { if !ok {
return return
@ -161,12 +164,18 @@ func route_delete_topic(w http.ResponseWriter, r *http.Request, user User) {
//log.Print("Topic #" + strconv.Itoa(tid) + " was deleted by User #" + strconv.Itoa(user.ID)) //log.Print("Topic #" + strconv.Itoa(tid) + " was deleted by User #" + strconv.Itoa(user.ID))
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
topicCreator, err := users.CascadeGet(topic.CreatedBy)
if err == nil {
wcount := wordCount(topic.Content) wcount := wordCount(topic.Content)
err = decrease_post_user_stats(wcount, topic.CreatedBy, true, user) err = topicCreator.decreasePostStats(wcount, true)
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
return return
} }
} else if err != ErrNoRows {
InternalError(err, w)
return
}
err = fstore.DecrementTopicCount(topic.ParentID) err = fstore.DecrementTopicCount(topic.ParentID)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
@ -192,7 +201,7 @@ func route_stick_topic(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
// TO-DO: Add hooks to make use of headerLite // TODO: Add hooks to make use of headerLite
_, ok := SimpleForumSessionCheck(w, r, &user, topic.ParentID) _, ok := SimpleForumSessionCheck(w, r, &user, topic.ParentID)
if !ok { if !ok {
return return
@ -248,7 +257,7 @@ func route_unstick_topic(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
// TO-DO: Add hooks to make use of headerLite // TODO: Add hooks to make use of headerLite
_, ok := SimpleForumSessionCheck(w, r, &user, topic.ParentID) _, ok := SimpleForumSessionCheck(w, r, &user, topic.ParentID)
if !ok { if !ok {
return return
@ -288,6 +297,8 @@ func route_unstick_topic(w http.ResponseWriter, r *http.Request, user User) {
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther) http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
} }
// TODO: Disable stat updates in posts handled by plugin_socialgroups
// TODO: Update the stats after edits so that we don't under or over decrement stats during deletes
func route_reply_edit_submit(w http.ResponseWriter, r *http.Request, user User) { func route_reply_edit_submit(w http.ResponseWriter, r *http.Request, user User) {
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
@ -327,7 +338,7 @@ func route_reply_edit_submit(w http.ResponseWriter, r *http.Request, user User)
return return
} }
// TO-DO: Add hooks to make use of headerLite // TODO: Add hooks to make use of headerLite
_, ok := SimpleForumSessionCheck(w, r, &user, fid) _, ok := SimpleForumSessionCheck(w, r, &user, fid)
if !ok { if !ok {
return return
@ -344,6 +355,7 @@ func route_reply_edit_submit(w http.ResponseWriter, r *http.Request, user User)
} }
} }
// TODO: Disable stat updates in posts handled by plugin_socialgroups
func route_reply_delete_submit(w http.ResponseWriter, r *http.Request, user User) { func route_reply_delete_submit(w http.ResponseWriter, r *http.Request, user User) {
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
@ -377,7 +389,7 @@ func route_reply_delete_submit(w http.ResponseWriter, r *http.Request, user User
return return
} }
// TO-DO: Add hooks to make use of headerLite // TODO: Add hooks to make use of headerLite
_, ok := SimpleForumSessionCheck(w, r, &user, fid) _, ok := SimpleForumSessionCheck(w, r, &user, fid)
if !ok { if !ok {
return return
@ -399,12 +411,18 @@ func route_reply_delete_submit(w http.ResponseWriter, r *http.Request, user User
w.Write(successJSONBytes) w.Write(successJSONBytes)
} }
replyCreator, err := users.CascadeGet(reply.CreatedBy)
if err == nil {
wcount := wordCount(reply.Content) wcount := wordCount(reply.Content)
err = decrease_post_user_stats(wcount, reply.CreatedBy, false, user) err = replyCreator.decreasePostStats(wcount, false)
if err != nil { if err != nil {
InternalErrorJSQ(err, w, r, isJs) InternalErrorJSQ(err, w, r, isJs)
return return
} }
} else if err != ErrNoRows {
InternalErrorJSQ(err, w, r, isJs)
return
}
_, err = remove_replies_from_topic_stmt.Exec(1, reply.ParentID) _, err = remove_replies_from_topic_stmt.Exec(1, reply.ParentID)
if err != nil { if err != nil {
InternalErrorJSQ(err, w, r, isJs) InternalErrorJSQ(err, w, r, isJs)
@ -524,7 +542,7 @@ func route_ips(w http.ResponseWriter, r *http.Request, user User) {
ip := html.EscapeString(r.URL.Path[len("/users/ips/"):]) ip := html.EscapeString(r.URL.Path[len("/users/ips/"):])
var uid int var uid int
var reqUserList map[int]bool = make(map[int]bool) var reqUserList = make(map[int]bool)
rows, err := find_users_by_ip_users_stmt.Query(ip) rows, err := find_users_by_ip_users_stmt.Query(ip)
if err != nil { if err != nil {
@ -590,14 +608,14 @@ func route_ips(w http.ResponseWriter, r *http.Request, user User) {
} }
// Convert the user ID map to a slice, then bulk load the users // Convert the user ID map to a slice, then bulk load the users
var idSlice []int = make([]int, len(reqUserList)) var idSlice = make([]int, len(reqUserList))
var i int var i int
for userID := range reqUserList { for userID := range reqUserList {
idSlice[i] = userID idSlice[i] = userID
i++ i++
} }
// TO-DO: What if a user is deleted via the Control Panel? // TODO: What if a user is deleted via the Control Panel?
userList, err := users.BulkCascadeGetMap(idSlice) userList, err := users.BulkCascadeGetMap(idSlice)
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
@ -616,7 +634,7 @@ func route_ips(w http.ResponseWriter, r *http.Request, user User) {
} }
} }
// TO-DO: This is being replaced with the new ban route system // TODO: This is being replaced with the new ban route system
/*func route_ban(w http.ResponseWriter, r *http.Request, user User) { /*func route_ban(w http.ResponseWriter, r *http.Request, user User) {
headerVars, ok := SessionCheck(w,r,&user) headerVars, ok := SessionCheck(w,r,&user)
if !ok { if !ok {

View File

@ -66,7 +66,7 @@ func _initDatabase() (err error) {
return err return err
} }
// TO-DO: Is there a less noisy way of doing this for tests? // TODO: Is there a less noisy way of doing this for tests?
log.Print("Preparing get_activity_feed_by_watcher statement.") log.Print("Preparing get_activity_feed_by_watcher statement.")
get_activity_feed_by_watcher_stmt, err = db.Prepare("SELECT activity_stream_matches.asid, activity_stream.actor, activity_stream.targetUser, activity_stream.event, activity_stream.elementType, activity_stream.elementID FROM `activity_stream_matches` INNER JOIN `activity_stream` ON activity_stream_matches.asid = activity_stream.asid AND activity_stream_matches.watcher != activity_stream.actor WHERE `watcher` = ? ORDER BY activity_stream.asid ASC LIMIT 8") get_activity_feed_by_watcher_stmt, err = db.Prepare("SELECT activity_stream_matches.asid, activity_stream.actor, activity_stream.targetUser, activity_stream.event, activity_stream.elementType, activity_stream.elementID FROM `activity_stream_matches` INNER JOIN `activity_stream` ON activity_stream_matches.asid = activity_stream.asid AND activity_stream_matches.watcher != activity_stream.actor WHERE `watcher` = ? ORDER BY activity_stream.asid ASC LIMIT 8")
if err != nil { if err != nil {

View File

@ -24,7 +24,7 @@ CREATE TABLE `forums`(
`active` tinyint DEFAULT 1 not null, `active` tinyint DEFAULT 1 not null,
`topicCount` int DEFAULT 0 not null, `topicCount` int DEFAULT 0 not null,
`preset` varchar(100) DEFAULT '' not null, `preset` varchar(100) DEFAULT '' not null,
`parentID` int DEFAULT 0 not null, /* TO-DO: Add support for subforums */ `parentID` int DEFAULT 0 not null, /* TODO: Add support for subforums */
`parentType` varchar(50) DEFAULT '' not null, `parentType` varchar(50) DEFAULT '' not null,
`lastTopic` varchar(100) DEFAULT '' not null, `lastTopic` varchar(100) DEFAULT '' not null,
`lastTopicID` int DEFAULT 0 not null, `lastTopicID` int DEFAULT 0 not null,
@ -181,16 +181,13 @@ CREATE TABLE `administration_logs`(
`doneAt` datetime not null `doneAt` datetime not null
); );
CREATE TABLE `sync`( INSERT INTO sync(`last_update`) VALUES (UTC_TIMESTAMP());
`last_update` int not null,
`node_id` int not null
);
INSERT INTO settings(`name`,`content`,`type`) VALUES ('url_tags','1','bool'); 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');
/* TO-DO: Implement the html-attribute setting type before deploying this */ /* TODO: Implement the html-attribute setting type before deploying this */
/*INSERT INTO settings(`name`,`content`,`type`) VALUES ('meta_desc','','html-attribute');*/ /*INSERT INTO settings(`name`,`content`,`type`) VALUES ('meta_desc','','html-attribute');*/
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);

View File

@ -6,33 +6,33 @@ import "errors"
import "net/http" import "net/http"
var wsHub WS_Hub var wsHub WS_Hub
var wsNouser error = errors.New("This user isn't connected via WebSockets") var errWsNouser = errors.New("This user isn't connected via WebSockets")
type WS_Hub struct { type WS_Hub struct {
} }
func (_ *WS_Hub) guest_count() int { func (_ *WS_Hub) guestCount() int {
return 0 return 0
} }
func (_ *WS_Hub) user_count() int { func (_ *WS_Hub) userCount() int {
return 0 return 0
} }
func (hub *WS_Hub) broadcast_message(_ string) error { func (hub *WS_Hub) broadcastMessage(_ string) error {
return nil return nil
} }
func (hub *WS_Hub) push_message(_ int, _ string) error { func (hub *WS_Hub) pushMessage(_ int, _ string) error {
return wsNouser return errWsNouser
} }
func (hub *WS_Hub) push_alert(_ int, _ int, _ string, _ string, _ int, _ int, _ int) error { func (hub *WS_Hub) pushAlert(_ int, _ int, _ string, _ string, _ int, _ int, _ int) error {
return wsNouser return errWsNouser
} }
func (hub *WS_Hub) push_alerts(_ []int, _ int, _ string, _ string, _ int, _ int, _ int) error { func (hub *WS_Hub) pushAlerts(_ []int, _ int, _ string, _ string, _ int, _ int, _ int) error {
return wsNouser return errWsNouser
} }
func route_websockets(_ http.ResponseWriter, _ *http.Request, _ User) { func route_websockets(_ http.ResponseWriter, _ *http.Request, _ User) {

View File

@ -15,11 +15,12 @@ type HeaderVars struct {
Widgets PageWidgets Widgets PageWidgets
Site *Site Site *Site
Settings map[string]interface{} Settings map[string]interface{}
Themes map[string]Theme // TODO: Use a slice containing every theme instead of the main map for speed
ThemeName string ThemeName string
ExtData ExtData ExtData ExtData
} }
// TO-DO: Add this to routes which don't use templates. E.g. Json APIs. // TODO: Add this to routes which don't use templates. E.g. Json APIs.
type HeaderLite struct { type HeaderLite struct {
Site *Site Site *Site
Settings SettingBox Settings SettingBox
@ -36,7 +37,7 @@ type PageWidgets struct {
items map[string]interface{} // Key: pluginname items map[string]interface{} // Key: pluginname
}*/ }*/
// TO-DO: Add a ExtDataHolder interface with methods for manipulating the contents? // TODO: Add a ExtDataHolder interface with methods for manipulating the contents?
type ExtData struct { type ExtData struct {
items map[string]interface{} // Key: pluginname items map[string]interface{} // Key: pluginname
sync.RWMutex sync.RWMutex
@ -531,7 +532,7 @@ func parseMessage(msg string /*, user User*/) string {
} }
outbytes = append(outbytes, urlOpen...) outbytes = append(outbytes, urlOpen...)
var urlBit = []byte(buildForumUrl("", fid)) var urlBit = []byte(buildForumURL("", fid))
outbytes = append(outbytes, urlBit...) outbytes = append(outbytes, urlBit...)
outbytes = append(outbytes, urlOpen2...) outbytes = append(outbytes, urlOpen2...)
var fidBit = []byte("#fid-" + strconv.Itoa(fid)) var fidBit = []byte("#fid-" + strconv.Itoa(fid))
@ -539,7 +540,7 @@ func parseMessage(msg string /*, user User*/) string {
outbytes = append(outbytes, urlClose...) outbytes = append(outbytes, urlClose...)
lastItem = i lastItem = i
} else { } else {
// TO-DO: Forum Shortcode Link // TODO: Forum Shortcode Link
} }
} else if msgbytes[i] == '@' { } else if msgbytes[i] == '@' {
//log.Print("IN @") //log.Print("IN @")
@ -798,7 +799,7 @@ func coerceIntBytes(data []byte) (res int, length int) {
return conv, i return conv, i
} }
// TO-DO: Write tests for this // TODO: Write tests for this
func paginate(count int, perPage int, maxPages int) []int { func paginate(count int, perPage int, maxPages int) []int {
if count < perPage { if count < perPage {
return []int{1} return []int{1}
@ -815,7 +816,7 @@ func paginate(count int, perPage int, maxPages int) []int {
return out return out
} }
// TO-DO: Write tests for this // TODO: Write tests for this
func pageOffset(count int, page int, perPage int) (int, int, int) { func pageOffset(count int, page int, perPage int) (int, int, int) {
var offset int var offset int
lastPage := (count / perPage) + 1 lastPage := (count / perPage) + 1

View File

@ -253,6 +253,7 @@ func route_panel_forums_create_submit(w http.ResponseWriter, r *http.Request, us
http.Redirect(w, r, "/panel/forums/", http.StatusSeeOther) http.Redirect(w, r, "/panel/forums/", http.StatusSeeOther)
} }
// TODO: Revamp this
func route_panel_forums_delete(w http.ResponseWriter, r *http.Request, user User, sfid string) { func route_panel_forums_delete(w http.ResponseWriter, r *http.Request, user User, sfid string) {
headerVars, stats, ok := PanelSessionCheck(w, r, &user) headerVars, stats, ok := PanelSessionCheck(w, r, &user)
if !ok { if !ok {
@ -282,8 +283,8 @@ func route_panel_forums_delete(w http.ResponseWriter, r *http.Request, user User
return return
} }
confirm_msg := "Are you sure you want to delete the '" + forum.Name + "' forum?" confirmMsg := "Are you sure you want to delete the '" + forum.Name + "' forum?"
yousure := AreYouSure{"/panel/forums/delete/submit/" + strconv.Itoa(fid), confirm_msg} yousure := AreYouSure{"/panel/forums/delete/submit/" + strconv.Itoa(fid), confirmMsg}
pi := PanelPage{"Delete Forum", user, headerVars, stats, tList, yousure} pi := PanelPage{"Delete Forum", user, headerVars, stats, tList, yousure}
if preRenderHooks["pre_render_panel_delete_forum"] != nil { if preRenderHooks["pre_render_panel_delete_forum"] != nil {
@ -406,10 +407,10 @@ func route_panel_forums_edit_submit(w http.ResponseWriter, r *http.Request, user
return return
} }
forum_name := r.PostFormValue("forum_name") forumName := r.PostFormValue("forum_name")
forum_desc := r.PostFormValue("forum_desc") forumDesc := r.PostFormValue("forum_desc")
forum_preset := stripInvalidPreset(r.PostFormValue("forum_preset")) forumPreset := stripInvalidPreset(r.PostFormValue("forum_preset"))
forum_active := r.PostFormValue("forum_active") forumActive := r.PostFormValue("forum_active")
forum, err := fstore.CascadeGet(fid) forum, err := fstore.CascadeGet(fid)
if err == ErrNoRows { if err == ErrNoRows {
@ -420,40 +421,40 @@ func route_panel_forums_edit_submit(w http.ResponseWriter, r *http.Request, user
return return
} }
if forum_name == "" { if forumName == "" {
forum_name = forum.Name forumName = forum.Name
} }
var active bool var active bool
if forum_active == "" { if forumActive == "" {
active = forum.Active active = forum.Active
} else if forum_active == "1" || forum_active == "Show" { } else if forumActive == "1" || forumActive == "Show" {
active = true active = true
} else { } else {
active = false active = false
} }
forumUpdateMutex.Lock() forumUpdateMutex.Lock()
_, err = update_forum_stmt.Exec(forum_name, forum_desc, active, forum_preset, fid) _, err = update_forum_stmt.Exec(forumName, forumDesc, active, forumPreset, fid)
if err != nil { if err != nil {
InternalErrorJSQ(err, w, r, isJs) InternalErrorJSQ(err, w, r, isJs)
return return
} }
if forum.Name != forum_name { if forum.Name != forumName {
forum.Name = forum_name forum.Name = forumName
} }
if forum.Desc != forum_desc { if forum.Desc != forumDesc {
forum.Desc = forum_desc forum.Desc = forumDesc
} }
if forum.Active != active { if forum.Active != active {
forum.Active = active forum.Active = active
} }
if forum.Preset != forum_preset { if forum.Preset != forumPreset {
forum.Preset = forum_preset forum.Preset = forumPreset
} }
forumUpdateMutex.Unlock() forumUpdateMutex.Unlock()
permmapToQuery(presetToPermmap(forum_preset), fid) permmapToQuery(presetToPermmap(forumPreset), fid)
if !isJs { if !isJs {
http.Redirect(w, r, "/panel/forums/", http.StatusSeeOther) http.Redirect(w, r, "/panel/forums/", http.StatusSeeOther)
@ -495,8 +496,8 @@ func route_panel_forums_edit_perms_submit(w http.ResponseWriter, r *http.Request
return return
} }
perm_preset := stripInvalidGroupForumPreset(r.PostFormValue("perm_preset")) permPreset := stripInvalidGroupForumPreset(r.PostFormValue("perm_preset"))
fperms, changed := groupForumPresetToForumPerms(perm_preset) fperms, changed := groupForumPresetToForumPerms(permPreset)
forum, err := fstore.CascadeGet(fid) forum, err := fstore.CascadeGet(fid)
if err == ErrNoRows { if err == ErrNoRows {
@ -519,7 +520,7 @@ func route_panel_forums_edit_perms_submit(w http.ResponseWriter, r *http.Request
return return
} }
_, err = add_forum_perms_to_group_stmt.Exec(gid, fid, perm_preset, perms) _, err = add_forum_perms_to_group_stmt.Exec(gid, fid, permPreset, perms)
if err != nil { if err != nil {
InternalErrorJSQ(err, w, r, isJs) InternalErrorJSQ(err, w, r, isJs)
return return
@ -552,7 +553,7 @@ func route_panel_settings(w http.ResponseWriter, r *http.Request, user User) {
} }
//log.Print("headerVars.Settings",headerVars.Settings) //log.Print("headerVars.Settings",headerVars.Settings)
var settingList map[string]interface{} = make(map[string]interface{}) var settingList = make(map[string]interface{})
rows, err := get_settings_stmt.Query() rows, err := get_settings_stmt.Query()
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
@ -560,6 +561,7 @@ func route_panel_settings(w http.ResponseWriter, r *http.Request, user User) {
} }
defer rows.Close() defer rows.Close()
// nolint need the type so people viewing this file understand what it returns without visiting setting.go
var settingLabels map[string]string = GetAllSettingLabels() var settingLabels map[string]string = GetAllSettingLabels()
var sname, scontent, stype string var sname, scontent, stype string
for rows.Next() { for rows.Next() {
@ -722,7 +724,7 @@ func route_panel_word_filters(w http.ResponseWriter, r *http.Request, user User)
return return
} }
var filterList WordFilterBox = wordFilterBox.Load().(WordFilterBox) var filterList = wordFilterBox.Load().(WordFilterBox)
pi := PanelPage{"Word Filter Manager", user, headerVars, stats, tList, filterList} pi := PanelPage{"Word Filter Manager", user, headerVars, stats, tList, filterList}
if preRenderHooks["pre_render_panel_word_filters"] != nil { if preRenderHooks["pre_render_panel_word_filters"] != nil {
if runPreRenderHook("pre_render_panel_word_filters", w, r, &user, &pi) { if runPreRenderHook("pre_render_panel_word_filters", w, r, &user, &pi) {
@ -766,14 +768,14 @@ func route_panel_word_filters_create(w http.ResponseWriter, r *http.Request, use
InternalErrorJSQ(err, w, r, isJs) InternalErrorJSQ(err, w, r, isJs)
return return
} }
lastId, err := res.LastInsertId() lastID, err := res.LastInsertId()
if err != nil { if err != nil {
InternalErrorJSQ(err, w, r, isJs) InternalErrorJSQ(err, w, r, isJs)
return return
} }
addWordFilter(int(lastId), find, replacement) addWordFilter(int(lastID), find, replacement)
http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther) // TO-DO: Return json for JS? http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther) // TODO: Return json for JS?
} }
func route_panel_word_filters_edit(w http.ResponseWriter, r *http.Request, user User, wfid string) { func route_panel_word_filters_edit(w http.ResponseWriter, r *http.Request, user User, wfid string) {
@ -811,7 +813,7 @@ func route_panel_word_filters_edit_submit(w http.ResponseWriter, r *http.Request
PreError("Bad Form", w, r) PreError("Bad Form", w, r)
return return
} }
// TO-DO: Either call it isJs or js rather than flip-flopping back and forth across the routes x.x // TODO: Either call it isJs or js rather than flip-flopping back and forth across the routes x.x
isJs := (r.PostFormValue("isJs") == "1") isJs := (r.PostFormValue("isJs") == "1")
if !user.Perms.EditSettings { if !user.Perms.EditSettings {
NoPermissionsJSQ(w, r, user, isJs) NoPermissionsJSQ(w, r, user, isJs)
@ -943,7 +945,7 @@ func route_panel_plugins_activate(w http.ResponseWriter, r *http.Request, user U
InternalError(err, w) InternalError(err, w)
return return
} }
var has_plugin bool = (err == nil) var hasPlugin = (err == nil)
if plugins[uname].Activate != nil { if plugins[uname].Activate != nil {
err = plugins[uname].Activate() err = plugins[uname].Activate()
@ -955,7 +957,7 @@ func route_panel_plugins_activate(w http.ResponseWriter, r *http.Request, user U
//log.Print("err",err) //log.Print("err",err)
//log.Print("active",active) //log.Print("active",active)
if has_plugin { if hasPlugin {
if active { if active {
LocalError("The plugin is already active", w, r, user) LocalError("The plugin is already active", w, r, user)
return return
@ -1071,7 +1073,7 @@ func route_panel_plugins_install(w http.ResponseWriter, r *http.Request, user Us
InternalError(err, w) InternalError(err, w)
return return
} }
var has_plugin bool = (err == nil) var hasPlugin = (err == nil)
if plugins[uname].Install != nil { if plugins[uname].Install != nil {
err = plugins[uname].Install() err = plugins[uname].Install()
@ -1089,7 +1091,7 @@ func route_panel_plugins_install(w http.ResponseWriter, r *http.Request, user Us
} }
} }
if has_plugin { if hasPlugin {
_, err = update_plugin_install_stmt.Exec(1, uname) _, err = update_plugin_install_stmt.Exec(1, uname)
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
@ -1139,7 +1141,7 @@ func route_panel_users(w http.ResponseWriter, r *http.Request, user User) {
} }
defer rows.Close() defer rows.Close()
// TO-DO: Add a UserStore method for iterating over global users and global user offsets // TODO: Add a UserStore method for iterating over global users and global user offsets
for rows.Next() { for rows.Next() {
puser := User{ID: 0} puser := User{ID: 0}
err := rows.Scan(&puser.ID, &puser.Name, &puser.Group, &puser.Active, &puser.IsSuperAdmin, &puser.Avatar) err := rows.Scan(&puser.ID, &puser.Name, &puser.Group, &puser.Active, &puser.IsSuperAdmin, &puser.Avatar)
@ -1485,7 +1487,7 @@ func route_panel_groups_edit_perms(w http.ResponseWriter, r *http.Request, user
return return
} }
// TO-DO: Load the phrases in bulk for efficiency? // TODO: Load the phrases in bulk for efficiency?
var localPerms []NameLangToggle var localPerms []NameLangToggle
localPerms = append(localPerms, NameLangToggle{"ViewTopic", GetLocalPermPhrase("ViewTopic"), group.Perms.ViewTopic}) localPerms = append(localPerms, NameLangToggle{"ViewTopic", GetLocalPermPhrase("ViewTopic"), group.Perms.ViewTopic})
localPerms = append(localPerms, NameLangToggle{"LikeItem", GetLocalPermPhrase("LikeItem"), group.Perms.LikeItem}) localPerms = append(localPerms, NameLangToggle{"LikeItem", GetLocalPermPhrase("LikeItem"), group.Perms.LikeItem})
@ -1877,7 +1879,9 @@ func route_panel_themes_set_default(w http.ResponseWriter, r *http.Request, user
} }
} }
// TO-DO: Make this less racey // TODO: Make this less racey
changeDefaultThemeMutex.Lock()
defaultTheme := defaultThemeBox.Load().(string)
_, err = update_theme_stmt.Exec(0, defaultTheme) _, err = update_theme_stmt.Exec(0, defaultTheme)
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
@ -1896,9 +1900,10 @@ func route_panel_themes_set_default(w http.ResponseWriter, r *http.Request, user
dTheme.Active = false dTheme.Active = false
themes[defaultTheme] = dTheme themes[defaultTheme] = dTheme
defaultTheme = uname // TO-DO: Make this less racey defaultThemeBox.Store(uname)
resetTemplateOverrides() resetTemplateOverrides()
mapThemeTemplates(theme) mapThemeTemplates(theme)
changeDefaultThemeMutex.Unlock()
http.Redirect(w, r, "/panel/themes/", http.StatusSeeOther) http.Redirect(w, r, "/panel/themes/", http.StatusSeeOther)
} }

View File

@ -508,7 +508,7 @@ func stripInvalidPreset(preset string) string {
} }
} }
// TO-DO: Move this into the phrase system? // TODO: Move this into the phrase system?
func presetToLang(preset string) string { func presetToLang(preset string) string {
switch preset { switch preset {
case "all": case "all":
@ -530,6 +530,7 @@ func presetToLang(preset string) string {
} }
} }
// TODO: Is this racey?
func rebuildGroupPermissions(gid int) error { func rebuildGroupPermissions(gid int) error {
var permstr []byte var permstr []byte
log.Print("Reloading a group") log.Print("Reloading a group")
@ -538,15 +539,15 @@ func rebuildGroupPermissions(gid int) error {
return err return err
} }
tmp_perms := Perms{ tmpPerms := Perms{
//ExtData: make(map[string]bool), //ExtData: make(map[string]bool),
} }
err = json.Unmarshal(permstr, &tmp_perms) err = json.Unmarshal(permstr, &tmpPerms)
if err != nil { if err != nil {
return err return err
} }
groups[gid].Perms = tmp_perms groups[gid].Perms = tmpPerms
return nil return nil
} }
@ -558,7 +559,7 @@ func overridePerms(perms *Perms, status bool) {
} }
} }
// TO-DO: We need a better way of overriding forum perms rather than setting them one by one // TODO: We need a better way of overriding forum perms rather than setting them one by one
func overrideForumPerms(perms *Perms, status bool) { func overrideForumPerms(perms *Perms, status bool) {
perms.ViewTopic = status perms.ViewTopic = status
perms.LikeItem = status perms.LikeItem = status

View File

@ -10,7 +10,7 @@ import "database/sql"
import _ "github.com/lib/pq" import _ "github.com/lib/pq"
import "./query_gen/lib" import "./query_gen/lib"
// TO-DO: Add support for SSL for all database drivers, not just pgsql // TODO: Add support for SSL for all database drivers, not just pgsql
var db_sslmode = "disable" // verify-full var db_sslmode = "disable" // verify-full
var get_activity_feed_by_watcher_stmt *sql.Stmt var get_activity_feed_by_watcher_stmt *sql.Stmt
var get_activity_count_by_watcher_stmt *sql.Stmt var get_activity_count_by_watcher_stmt *sql.Stmt
@ -24,7 +24,7 @@ func init() {
} }
func _init_database() (err error) { func _init_database() (err error) {
// TO-DO: Investigate connect_timeout to see what it does exactly and whether it's relevant to us // TODO: Investigate connect_timeout to see what it does exactly and whether it's relevant to us
var _dbpassword string var _dbpassword string
if(dbpassword != ""){ if(dbpassword != ""){
_dbpassword = " password='" + _escape_bit(db_config.Password) + "'" _dbpassword = " password='" + _escape_bit(db_config.Password) + "'"
@ -68,6 +68,6 @@ func _init_database() (err error) {
} }
func _escape_bit(bit string) string { func _escape_bit(bit string) string {
// TO-DO: Write a custom parser, so that backslashes work properly in the sql.Open string. Do something similar for the database driver, if possible? // TODO: Write a custom parser, so that backslashes work properly in the sql.Open string. Do something similar for the database driver, if possible?
return strings.Replace(bit,"'","\\'",-1) return strings.Replace(bit,"'","\\'",-1)
} }

View File

@ -11,7 +11,8 @@ import (
"sync/atomic" "sync/atomic"
) )
// TO-DO: 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
var changeLangpackMutex sync.Mutex var changeLangpackMutex sync.Mutex
var currentLanguage = "english" var currentLanguage = "english"
var currentLangPack atomic.Value var currentLangPack atomic.Value
@ -33,7 +34,8 @@ type LanguagePack struct {
SettingLabels map[string]string SettingLabels map[string]string
} }
// TO-DO: Move the english language pack into it's own file and just keep the common logic here // TODO: Add the ability to edit language JSON files from the Control Panel and automatically scan the files for changes
// TODO: Move the english language pack into a JSON file and load that on start-up
var langpacks = map[string]*LanguagePack{ var langpacks = map[string]*LanguagePack{
"english": &LanguagePack{ "english": &LanguagePack{
Name: "english", Name: "english",
@ -135,7 +137,8 @@ func DeletePhrase() {
} }
// TO-DO: Use atomics to store the pointer of the current active langpack? // TODO: Use atomics to store the pointer of the current active langpack?
// nolint
func ChangeLanguagePack(name string) (exists bool) { func ChangeLanguagePack(name string) (exists bool) {
changeLangpackMutex.Lock() changeLangpackMutex.Lock()
pack, ok := langpacks[name] pack, ok := langpacks[name]

View File

@ -203,9 +203,8 @@ func bbcodeParseWithoutCode(msg string) string {
msg = bbcodeURLLabel.ReplaceAllString(msg, "<a href='$1$2//$3' rel='nofollow'>$4</i>") msg = bbcodeURLLabel.ReplaceAllString(msg, "<a href='$1$2//$3' rel='nofollow'>$4</i>")
msg = bbcodeQuotes.ReplaceAllString(msg, "<span class='postQuote'>$1</span>") msg = bbcodeQuotes.ReplaceAllString(msg, "<span class='postQuote'>$1</span>")
return bbcodeCode.ReplaceAllString(msg, "<span class='codequotes'>$1</span>") return bbcodeCode.ReplaceAllString(msg, "<span class='codequotes'>$1</span>")
} else {
return string(msgbytes)
} }
return string(msgbytes)
} }
// Does every type of BBCode // Does every type of BBCode
@ -368,7 +367,7 @@ func bbcodeFullParse(msg string) string {
goto MainLoop goto MainLoop
} }
// TO-DO: Add support for negative numbers? // TODO: Add support for negative numbers?
if number < 0 { if number < 0 {
outbytes = append(outbytes, bbcodeNoNegative...) outbytes = append(outbytes, bbcodeNoNegative...)
goto MainLoop goto MainLoop

View File

@ -4,7 +4,7 @@ package main
import "regexp" import "regexp"
import "strings" import "strings"
var markdownMaxDepth int = 25 // How deep the parser will go when parsing Markdown strings var markdownMaxDepth = 25 // How deep the parser will go when parsing Markdown strings
var markdownUnclosedElement []byte var markdownUnclosedElement []byte
var markdownBoldTagOpen, markdownBoldTagClose []byte var markdownBoldTagOpen, markdownBoldTagClose []byte
@ -88,7 +88,7 @@ func _markdownParse(msg string, n int) string {
switch msg[index] { switch msg[index] {
case '_': case '_':
var startIndex int = index var startIndex = index
if (index + 1) >= len(msg) { if (index + 1) >= len(msg) {
break break
} }
@ -111,7 +111,7 @@ func _markdownParse(msg string, n int) string {
lastElement = index lastElement = index
index-- index--
case '~': case '~':
var startIndex int = index var startIndex = index
if (index + 1) >= len(msg) { if (index + 1) >= len(msg) {
break break
} }
@ -142,8 +142,8 @@ func _markdownParse(msg string, n int) string {
//log.Print("start string(msg[index])",string(msg[index])) //log.Print("start string(msg[index])",string(msg[index]))
//log.Print("start []byte(msg[:index])",[]byte(msg[:index])) //log.Print("start []byte(msg[:index])",[]byte(msg[:index]))
var startIndex int = index var startIndex = index
var italic bool = true var italic = true
var bold bool var bold bool
if (index + 2) < len(msg) { if (index + 2) < len(msg) {
//log.Print("start index + 1",index + 1) //log.Print("start index + 1",index + 1)
@ -339,7 +339,7 @@ SwitchLoop:
// plugin_markdown doesn't support lists yet, but I want it to be easy to have nested lists when we do have them // plugin_markdown doesn't support lists yet, but I want it to be easy to have nested lists when we do have them
func markdownSkipList(data string, index int) int { func markdownSkipList(data string, index int) int {
var lastNewline int var lastNewline int
var datalen int = len(data) var datalen = len(data)
for ; index < datalen; index++ { for ; index < datalen; index++ {
SkipListInnerLoop: SkipListInnerLoop:

View File

@ -25,7 +25,7 @@ var socialgroupsAttachForumStmt *sql.Stmt
var socialgroupsUnattachForumStmt *sql.Stmt var socialgroupsUnattachForumStmt *sql.Stmt
var socialgroupsAddMemberStmt *sql.Stmt var socialgroupsAddMemberStmt *sql.Stmt
// TO-DO: Add a better way of splitting up giant plugins like this // TODO: Add a better way of splitting up giant plugins like this
// SocialGroup is a struct representing a social group // SocialGroup is a struct representing a social group
type SocialGroup struct { type SocialGroup struct {
@ -87,12 +87,12 @@ type SocialGroupMember struct {
RankString string /* Member, Mod, Admin, Owner */ RankString string /* Member, Mod, Admin, Owner */
PostCount int PostCount int
JoinedAt string JoinedAt string
Offline bool // TO-DO: Need to track the online states of members when WebSockets are enabled Offline bool // TODO: Need to track the online states of members when WebSockets are enabled
User User User User
} }
// TO-DO: Add a plugin interface instead of having a bunch of argument to AddPlugin? // TODO: Add a plugin interface instead of having a bunch of argument to AddPlugin?
func init() { func init() {
plugins["socialgroups"] = NewPlugin("socialgroups", "Social Groups", "Azareal", "http://github.com/Azareal", "", "", "", initSocialgroups, nil, deactivateSocialgroups, installSocialgroups, nil) plugins["socialgroups"] = NewPlugin("socialgroups", "Social Groups", "Azareal", "http://github.com/Azareal", "", "", "", initSocialgroups, nil, deactivateSocialgroups, installSocialgroups, nil)
} }
@ -104,7 +104,7 @@ func initSocialgroups() (err error) {
plugins["socialgroups"].AddHook("pre_render_view_forum", socialgroupsPreRenderViewForum) plugins["socialgroups"].AddHook("pre_render_view_forum", socialgroupsPreRenderViewForum)
plugins["socialgroups"].AddHook("simple_forum_check_pre_perms", socialgroupsForumCheck) plugins["socialgroups"].AddHook("simple_forum_check_pre_perms", socialgroupsForumCheck)
plugins["socialgroups"].AddHook("forum_check_pre_perms", socialgroupsForumCheck) plugins["socialgroups"].AddHook("forum_check_pre_perms", socialgroupsForumCheck)
// TO-DO: Auto-grant this perm to admins upon installation? // TODO: Auto-grant this perm to admins upon installation?
registerPluginPerm("CreateSocialGroup") registerPluginPerm("CreateSocialGroup")
router.HandleFunc("/groups/", socialgroupsGroupList) router.HandleFunc("/groups/", socialgroupsGroupList)
router.HandleFunc("/group/", socialgroupsViewGroup) router.HandleFunc("/group/", socialgroupsViewGroup)
@ -175,7 +175,7 @@ func deactivateSocialgroups() {
_ = socialgroupsAddMemberStmt.Close() _ = socialgroupsAddMemberStmt.Close()
} }
// TO-DO: Stop accessing the query builder directly and add a feature in Gosora which is more easily reversed, if an error comes up during the installation process // TODO: Stop accessing the query builder directly and add a feature in Gosora which is more easily reversed, if an error comes up during the installation process
func installSocialgroups() error { func installSocialgroups() error {
sgTableStmt, err := qgen.Builder.CreateTable("socialgroups", "utf8mb4", "utf8mb4_general_ci", sgTableStmt, err := qgen.Builder.CreateTable("socialgroups", "utf8mb4", "utf8mb4_general_ci",
[]qgen.DB_Table_Column{ []qgen.DB_Table_Column{
@ -229,9 +229,9 @@ func uninstallSocialgroups() error {
return nil return nil
} }
// TO-DO: Do this properly via the widget system // TODO: Do this properly via the widget system
func socialgroupsCommonAreaWidgets(headerVars *HeaderVars) { func socialgroupsCommonAreaWidgets(headerVars *HeaderVars) {
// TO-DO: Hot Groups? Featured Groups? Official Groups? // TODO: Hot Groups? Featured Groups? Official Groups?
var b bytes.Buffer var b bytes.Buffer
var menu = WidgetMenu{"Social Groups", []WidgetMenuItem{ var menu = WidgetMenu{"Social Groups", []WidgetMenuItem{
WidgetMenuItem{"Create Group", "/group/create/", false}, WidgetMenuItem{"Create Group", "/group/create/", false},
@ -243,15 +243,15 @@ func socialgroupsCommonAreaWidgets(headerVars *HeaderVars) {
return return
} }
if themes[defaultTheme].Sidebars == "left" { if themes[headerVars.ThemeName].Sidebars == "left" {
headerVars.Widgets.LeftSidebar = template.HTML(string(b.Bytes())) headerVars.Widgets.LeftSidebar = template.HTML(string(b.Bytes()))
} else if themes[defaultTheme].Sidebars == "right" || themes[defaultTheme].Sidebars == "both" { } else if themes[headerVars.ThemeName].Sidebars == "right" || themes[headerVars.ThemeName].Sidebars == "both" {
headerVars.Widgets.RightSidebar = template.HTML(string(b.Bytes())) headerVars.Widgets.RightSidebar = template.HTML(string(b.Bytes()))
} }
} }
// TO-DO: Do this properly via the widget system // TODO: Do this properly via the widget system
// TO-DO: Make a better more customisable group widget system // TODO: Make a better more customisable group widget system
func socialgroupsGroupWidgets(headerVars *HeaderVars, sgItem *SocialGroup) (success bool) { func socialgroupsGroupWidgets(headerVars *HeaderVars, sgItem *SocialGroup) (success bool) {
return false // Disabled until the next commit return false // Disabled until the next commit
@ -267,9 +267,9 @@ func socialgroupsGroupWidgets(headerVars *HeaderVars, sgItem *SocialGroup) (succ
return false return false
} }
if themes[defaultTheme].Sidebars == "left" { if themes[headerVars.ThemeName].Sidebars == "left" {
headerVars.Widgets.LeftSidebar = template.HTML(string(b.Bytes())) headerVars.Widgets.LeftSidebar = template.HTML(string(b.Bytes()))
} else if themes[defaultTheme].Sidebars == "right" || themes[defaultTheme].Sidebars == "both" { } else if themes[headerVars.ThemeName].Sidebars == "right" || themes[headerVars.ThemeName].Sidebars == "both" {
headerVars.Widgets.RightSidebar = template.HTML(string(b.Bytes())) headerVars.Widgets.RightSidebar = template.HTML(string(b.Bytes()))
} else { } else {
return false return false
@ -356,7 +356,7 @@ func socialgroupsCreateGroup(w http.ResponseWriter, r *http.Request, user User)
if !ok { if !ok {
return return
} }
// TO-DO: Add an approval queue mode for group creation // TODO: Add an approval queue mode for group creation
if !user.Loggedin || !user.PluginPerms["CreateSocialGroup"] { if !user.Loggedin || !user.PluginPerms["CreateSocialGroup"] {
NoPermissions(w, r, user) NoPermissions(w, r, user)
return return
@ -371,7 +371,7 @@ func socialgroupsCreateGroup(w http.ResponseWriter, r *http.Request, user User)
} }
func socialgroupsCreateGroupSubmit(w http.ResponseWriter, r *http.Request, user User) { func socialgroupsCreateGroupSubmit(w http.ResponseWriter, r *http.Request, user User) {
// TO-DO: Add an approval queue mode for group creation // TODO: Add an approval queue mode for group creation
if !user.Loggedin || !user.PluginPerms["CreateSocialGroup"] { if !user.Loggedin || !user.PluginPerms["CreateSocialGroup"] {
NoPermissions(w, r, user) NoPermissions(w, r, user)
return return
@ -561,7 +561,7 @@ func socialgroupsTrowAssign(args ...interface{}) interface{} {
return nil return nil
} }
// TO-DO: 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 socialgroupsTopicCreatePreLoop(args ...interface{}) interface{} { func socialgroupsTopicCreatePreLoop(args ...interface{}) interface{} {
var fid = args[2].(int) var fid = args[2].(int)
if fstore.DirtyGet(fid).ParentType == "socialgroup" { if fstore.DirtyGet(fid).ParentType == "socialgroup" {
@ -571,9 +571,9 @@ func socialgroupsTopicCreatePreLoop(args ...interface{}) interface{} {
return nil return nil
} }
// TO-DO: Add privacy options // TODO: Add privacy options
// TO-DO: Add support for multiple boards and add per-board simplified permissions // TODO: Add support for multiple boards and add per-board simplified permissions
// TO-DO: Take isJs into account for routes which expect JSON responses // TODO: Take isJs into account for routes which expect JSON responses
func socialgroupsForumCheck(args ...interface{}) (skip interface{}) { func socialgroupsForumCheck(args ...interface{}) (skip interface{}) {
var r = args[1].(*http.Request) var r = args[1].(*http.Request)
var fid = args[3].(*int) var fid = args[3].(*int)
@ -604,10 +604,10 @@ func socialgroupsForumCheck(args ...interface{}) (skip interface{}) {
var posts int var posts int
var joinedAt string var joinedAt string
// TO-DO: Group privacy settings. For now, groups are all globally visible // TODO: Group privacy settings. For now, groups are all globally visible
// Clear the default group permissions // Clear the default group permissions
// TO-DO: Do this more efficiently, doing it quick and dirty for now to get this out quickly // TODO: Do this more efficiently, doing it quick and dirty for now to get this out quickly
overrideForumPerms(&user.Perms, false) overrideForumPerms(&user.Perms, false)
user.Perms.ViewTopic = true user.Perms.ViewTopic = true
@ -620,7 +620,7 @@ func socialgroupsForumCheck(args ...interface{}) (skip interface{}) {
return true return true
} }
// TO-DO: Implement bans properly by adding the Local Ban API in the next commit // TODO: Implement bans properly by adding the Local Ban API in the next commit
if rank < 0 { if rank < 0 {
return true return true
} }
@ -641,7 +641,7 @@ func socialgroupsForumCheck(args ...interface{}) (skip interface{}) {
return false return false
} }
// TO-DO: Override redirects? I don't think this is needed quite yet // TODO: Override redirects? I don't think this is needed quite yet
func socialgroupsWidgets(args ...interface{}) interface{} { func socialgroupsWidgets(args ...interface{}) interface{} {
var zone = args[0].(string) var zone = args[0].(string)

View File

@ -3,7 +3,7 @@ package main
import "strconv" import "strconv"
import "testing" import "testing"
// TO-DO: Replace the soft tabs with hard ones // TODO: Replace the soft tabs with hard ones
// go test -v // go test -v
type ME_Pair struct { type ME_Pair struct {

View File

@ -19,7 +19,7 @@ function bind_to_alerts() {
}); });
} }
// TO-DO: Add the ability for users to dismiss alerts // TODO: Add the ability for users to dismiss alerts
function load_alerts(menu_alerts) function load_alerts(menu_alerts)
{ {
var alertListNode = menu_alerts.getElementsByClassName("alertList")[0]; var alertListNode = menu_alerts.getElementsByClassName("alertList")[0];
@ -119,7 +119,7 @@ $(document).ready(function(){
conn.onopen = function() { conn.onopen = function() {
conn.send("page " + document.location.pathname + '\r'); conn.send("page " + document.location.pathname + '\r');
// TO-DO: Don't ask again, if it's denied. We could have a setting in the UCP which automatically requests this when someone flips desktop notifications on // TODO: Don't ask again, if it's denied. We could have a setting in the UCP which automatically requests this when someone flips desktop notifications on
Notification.requestPermission(); Notification.requestPermission();
} }
conn.onclose = function() { conn.onclose = function() {
@ -148,15 +148,15 @@ $(document).ready(function(){
for (var i = 0; i < alertList.length; i++) alist += alertList[i]; for (var i = 0; i < alertList.length; i++) alist += alertList[i];
//console.log(alist); //console.log(alist);
// TO-DO: Add support for other alert feeds like PM Alerts // TODO: Add support for other alert feeds like PM Alerts
var general_alerts = document.getElementById("general_alerts"); var general_alerts = document.getElementById("general_alerts");
var alertListNode = general_alerts.getElementsByClassName("alertList")[0]; var alertListNode = general_alerts.getElementsByClassName("alertList")[0];
var alertCounterNode = general_alerts.getElementsByClassName("alert_counter")[0]; var alertCounterNode = general_alerts.getElementsByClassName("alert_counter")[0];
alertListNode.innerHTML = alist; alertListNode.innerHTML = alist;
alertCounterNode.textContent = alertCount; alertCounterNode.textContent = alertCount;
// TO-DO: Add some sort of notification queue to avoid flooding the end-user with notices? // TODO: Add some sort of notification queue to avoid flooding the end-user with notices?
// TO-DO: Use the site name instead of "Something Happened" // TODO: Use the site name instead of "Something Happened"
if(Notification.permission === "granted") { if(Notification.permission === "granted") {
var n = new Notification("Something Happened",{ var n = new Notification("Something Happened",{
body: msg, body: msg,
@ -354,7 +354,7 @@ $(document).ready(function(){
}); });
// This one's for Tempra Conflux // This one's for Tempra Conflux
// TO-DO: We might want to use pure JS here // TODO: We might want to use pure JS here
$(".ip_item").each(function(){ $(".ip_item").each(function(){
var ip = this.textContent; var ip = this.textContent;
if(ip.length > 10){ if(ip.length > 10){
@ -396,6 +396,32 @@ $(document).ready(function(){
event.stopPropagation(); event.stopPropagation();
}) })
$("#themeSelectorSelect").change(function(){
console.log("Changing the theme to " + this.options[this.selectedIndex].getAttribute("val"));
$.ajax({
url: this.form.getAttribute("action") + "?session=" + session,
type: "POST",
dataType: "json",
data: { "newTheme": this.options[this.selectedIndex].getAttribute("val"), isJs: "1" },
success: function (data, status, xhr) {
console.log("Theme successfully switched");
console.log("data",data);
console.log("status",status);
window.location.reload();
},
// TODO: Use a standard error handler for the AJAX calls in here which throws up the response (if JSON) in a .notice? Might be difficult to trace errors in the console, if we reuse the same function every-time
error: function(xhr,status,errstr) {
console.log("The AJAX request failed");
console.log("xhr",xhr);
console.log("status",status);
console.log("errstr",errstr);
if(status=="parsererror") {
console.log("The server didn't respond with a valid JSON response");
}
}
});
});
this.onkeyup = function(event) { this.onkeyup = function(event) {
if(event.which == 37) this.querySelectorAll("#prevFloat a")[0].click(); if(event.which == 37) this.querySelectorAll("#prevFloat a")[0].click();
if(event.which == 39) this.querySelectorAll("#nextFloat a")[0].click(); if(event.which == 39) this.querySelectorAll("#nextFloat a")[0].click();

View File

@ -14,7 +14,7 @@ type DB_Install_Instruction struct {
} }
// 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
// TO-DO: 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 DB_Adapter adapter DB_Adapter
instructions []DB_Install_Instruction instructions []DB_Install_Instruction

View File

@ -54,7 +54,7 @@ func (adapter *Mysql_Adapter) CreateTable(name string, table string, charset str
} }
var end string var end string
// TO-DO: Exclude the other variants of text like mediumtext and longtext too // TODO: Exclude the other variants of text like mediumtext and longtext too
if column.Default != "" && column.Type != "text" { if column.Default != "" && column.Type != "text" {
end = " DEFAULT " end = " DEFAULT "
if adapter.stringyType(column.Type) && column.Default != "''" { if adapter.stringyType(column.Type) && column.Default != "''" {
@ -814,7 +814,7 @@ func (adapter *Mysql_Adapter) Write() error {
var stmts, body string var stmts, body string
for _, name := range adapter.BufferOrder { for _, name := range adapter.BufferOrder {
stmt := adapter.Buffer[name] stmt := adapter.Buffer[name]
// TO-DO: Add support for create-table? Table creation might be a little complex for Go to do outside a SQL file :( // TODO: Add support for create-table? Table creation might be a little complex for Go to do outside a SQL file :(
if stmt.Type != "create-table" { if stmt.Type != "create-table" {
stmts += "var " + name + "_stmt *sql.Stmt\n" stmts += "var " + name + "_stmt *sql.Stmt\n"
body += ` body += `

View File

@ -29,7 +29,7 @@ func (adapter *Pgsql_Adapter) GetStmts() map[string]DB_Stmt {
return adapter.Buffer return adapter.Buffer
} }
// TO-DO: Implement this // TODO: Implement this
// We may need to change the CreateTable API to better suit PGSQL and the other database drivers which are coming up // We may need to change the CreateTable API to better suit PGSQL and the other database drivers which are coming up
func (adapter *Pgsql_Adapter) CreateTable(name string, table string, charset string, collation string, columns []DB_Table_Column, keys []DB_Table_Key) (string, error) { func (adapter *Pgsql_Adapter) CreateTable(name string, table string, charset string, collation string, columns []DB_Table_Column, keys []DB_Table_Key) (string, error) {
if name == "" { if name == "" {
@ -93,7 +93,7 @@ func (adapter *Pgsql_Adapter) CreateTable(name string, table string, charset str
return querystr, nil return querystr, nil
} }
// TO-DO: Implement this // TODO: Implement this
func (adapter *Pgsql_Adapter) SimpleInsert(name string, table string, columns string, fields string) (string, error) { func (adapter *Pgsql_Adapter) SimpleInsert(name string, table string, columns string, fields string) (string, error) {
if name == "" { if name == "" {
return "", errors.New("You need a name for this statement") return "", errors.New("You need a name for this statement")
@ -110,7 +110,7 @@ func (adapter *Pgsql_Adapter) SimpleInsert(name string, table string, columns st
return "", nil return "", nil
} }
// TO-DO: Implement this // TODO: Implement this
func (adapter *Pgsql_Adapter) SimpleReplace(name string, table string, columns string, fields string) (string, error) { func (adapter *Pgsql_Adapter) SimpleReplace(name string, table string, columns string, fields string) (string, error) {
if name == "" { if name == "" {
return "", errors.New("You need a name for this statement") return "", errors.New("You need a name for this statement")
@ -127,7 +127,7 @@ func (adapter *Pgsql_Adapter) SimpleReplace(name string, table string, columns s
return "", nil return "", nil
} }
// TO-DO: Implemented, but we need CreateTable and a better installer to *test* it // TODO: Implemented, but we need CreateTable and a better installer to *test* it
func (adapter *Pgsql_Adapter) SimpleUpdate(name string, table string, set string, where string) (string, error) { func (adapter *Pgsql_Adapter) SimpleUpdate(name string, table string, set string, where string) (string, error) {
if name == "" { if name == "" {
return "", errors.New("You need a name for this statement") return "", errors.New("You need a name for this statement")
@ -144,7 +144,7 @@ func (adapter *Pgsql_Adapter) SimpleUpdate(name string, table string, set string
for _, token := range item.Expr { for _, token := range item.Expr {
switch token.Type { switch token.Type {
case "function": case "function":
// TO-DO: Write a more sophisticated function parser on the utils side. // TODO: Write a more sophisticated function parser on the utils side.
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "LOCALTIMESTAMP()" token.Contents = "LOCALTIMESTAMP()"
} }
@ -170,7 +170,7 @@ func (adapter *Pgsql_Adapter) SimpleUpdate(name string, table string, set string
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch token.Type { switch token.Type {
case "function": case "function":
// TO-DO: Write a more sophisticated function parser on the utils side. What's the situation in regards to case sensitivity? // TODO: Write a more sophisticated function parser on the utils side. What's the situation in regards to case sensitivity?
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "LOCALTIMESTAMP()" token.Contents = "LOCALTIMESTAMP()"
} }
@ -194,7 +194,7 @@ func (adapter *Pgsql_Adapter) SimpleUpdate(name string, table string, set string
return querystr, nil return querystr, nil
} }
// TO-DO: Implement this // TODO: Implement this
func (adapter *Pgsql_Adapter) SimpleDelete(name string, table string, where string) (string, error) { func (adapter *Pgsql_Adapter) SimpleDelete(name string, table string, where string) (string, error) {
if name == "" { if name == "" {
return "", errors.New("You need a name for this statement") return "", errors.New("You need a name for this statement")
@ -208,7 +208,7 @@ func (adapter *Pgsql_Adapter) SimpleDelete(name string, table string, where stri
return "", nil return "", nil
} }
// TO-DO: Implement this // TODO: Implement this
// We don't want to accidentally wipe tables, so we'll have a seperate method for purging tables instead // We don't want to accidentally wipe tables, so we'll have a seperate method for purging tables instead
func (adapter *Pgsql_Adapter) Purge(name string, table string) (string, error) { func (adapter *Pgsql_Adapter) Purge(name string, table string) (string, error) {
if name == "" { if name == "" {
@ -220,7 +220,7 @@ func (adapter *Pgsql_Adapter) Purge(name string, table string) (string, error) {
return "", nil return "", nil
} }
// TO-DO: Implement this // TODO: Implement this
func (adapter *Pgsql_Adapter) SimpleSelect(name string, table string, columns string, where string, orderby string, limit string) (string, error) { func (adapter *Pgsql_Adapter) SimpleSelect(name string, table string, columns string, where string, orderby string, limit string) (string, error) {
if name == "" { if name == "" {
return "", errors.New("You need a name for this statement") return "", errors.New("You need a name for this statement")
@ -234,7 +234,7 @@ func (adapter *Pgsql_Adapter) SimpleSelect(name string, table string, columns st
return "", nil return "", nil
} }
// TO-DO: Implement this // TODO: Implement this
func (adapter *Pgsql_Adapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) { func (adapter *Pgsql_Adapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
if name == "" { if name == "" {
return "", errors.New("You need a name for this statement") return "", errors.New("You need a name for this statement")
@ -254,7 +254,7 @@ func (adapter *Pgsql_Adapter) SimpleLeftJoin(name string, table1 string, table2
return "", nil return "", nil
} }
// TO-DO: Implement this // TODO: Implement this
func (adapter *Pgsql_Adapter) SimpleInnerJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) { func (adapter *Pgsql_Adapter) SimpleInnerJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
if name == "" { if name == "" {
return "", errors.New("You need a name for this statement") return "", errors.New("You need a name for this statement")
@ -274,22 +274,22 @@ func (adapter *Pgsql_Adapter) SimpleInnerJoin(name string, table1 string, table2
return "", nil return "", nil
} }
// TO-DO: Implement this // TODO: Implement this
func (adapter *Pgsql_Adapter) SimpleInsertSelect(name string, ins DB_Insert, sel DB_Select) (string, error) { func (adapter *Pgsql_Adapter) SimpleInsertSelect(name string, ins DB_Insert, sel DB_Select) (string, error) {
return "", nil return "", nil
} }
// TO-DO: Implement this // TODO: Implement this
func (adapter *Pgsql_Adapter) SimpleInsertLeftJoin(name string, ins DB_Insert, sel DB_Join) (string, error) { func (adapter *Pgsql_Adapter) SimpleInsertLeftJoin(name string, ins DB_Insert, sel DB_Join) (string, error) {
return "", nil return "", nil
} }
// TO-DO: Implement this // TODO: Implement this
func (adapter *Pgsql_Adapter) SimpleInsertInnerJoin(name string, ins DB_Insert, sel DB_Join) (string, error) { func (adapter *Pgsql_Adapter) SimpleInsertInnerJoin(name string, ins DB_Insert, sel DB_Join) (string, error) {
return "", nil return "", nil
} }
// TO-DO: Implement this // TODO: Implement this
func (adapter *Pgsql_Adapter) SimpleCount(name string, table string, where string, limit string) (string, error) { func (adapter *Pgsql_Adapter) SimpleCount(name string, table string, where string, limit string) (string, error) {
if name == "" { if name == "" {
return "", errors.New("You need a name for this statement") return "", errors.New("You need a name for this statement")
@ -304,7 +304,7 @@ func (adapter *Pgsql_Adapter) Write() error {
var stmts, body string var stmts, body string
for _, name := range adapter.BufferOrder { for _, name := range adapter.BufferOrder {
stmt := adapter.Buffer[name] stmt := adapter.Buffer[name]
// TO-DO: Add support for create-table? Table creation might be a little complex for Go to do outside a SQL file :( // TODO: Add support for create-table? Table creation might be a little complex for Go to do outside a SQL file :(
if stmt.Type != "create-table" { if stmt.Type != "create-table" {
stmts += "var " + name + "_stmt *sql.Stmt\n" stmts += "var " + name + "_stmt *sql.Stmt\n"
body += ` body += `

View File

@ -121,7 +121,7 @@ type DB_Adapter interface {
SimpleCount(string,string,string,string) (string, error) SimpleCount(string,string,string,string) (string, error)
Write() error Write() error
// TO-DO: Add a simple query builder // TODO: Add a simple query builder
} }
func GetAdapter(name string) (adap DB_Adapter, err error) { func GetAdapter(name string) (adap DB_Adapter, err error) {

View File

@ -15,6 +15,7 @@ func main() {
} }
} }
// nolint
func write_statements(adapter qgen.DB_Adapter) error { func write_statements(adapter qgen.DB_Adapter) error {
err := create_tables(adapter) err := create_tables(adapter)
if err != nil { if err != nil {
@ -84,6 +85,7 @@ func write_statements(adapter qgen.DB_Adapter) error {
return nil return nil
} }
// nolint
func create_tables(adapter qgen.DB_Adapter) error { func create_tables(adapter qgen.DB_Adapter) error {
qgen.Install.CreateTable("users", "utf8mb4", "utf8mb4_general_ci", qgen.Install.CreateTable("users", "utf8mb4", "utf8mb4_general_ci",
[]qgen.DB_Table_Column{ []qgen.DB_Table_Column{
@ -120,10 +122,10 @@ func create_tables(adapter qgen.DB_Adapter) error {
// What should we do about global penalties? Put them on the users table for speed? Or keep them here? // What should we do about global penalties? Put them on the users table for speed? Or keep them here?
// Should we add IP Penalties? No, that's a stupid idea, just implement IP Bans properly. What about shadowbans? // Should we add IP Penalties? No, that's a stupid idea, just implement IP Bans properly. What about shadowbans?
// TO-DO: Perm overrides // TODO: Perm overrides
// TO-DO: Add a mod-queue and other basic auto-mod features. This is needed for awaiting activation and the mod_queue penalty flag // TODO: Add a mod-queue and other basic auto-mod features. This is needed for awaiting activation and the mod_queue penalty flag
// TO-DO: Add a penalty type where a user is stopped from creating plugin_socialgroups social groups // TODO: Add a penalty type where a user is stopped from creating plugin_socialgroups social groups
// TO-DO: Shadow bans. We will probably have a CanShadowBan permission for this, as we *really* don't want people using this lightly. // TODO: Shadow bans. We will probably have a CanShadowBan permission for this, as we *really* don't want people using this lightly.
/*qgen.Install.CreateTable("users_penalties","","", /*qgen.Install.CreateTable("users_penalties","","",
[]qgen.DB_Table_Column{ []qgen.DB_Table_Column{
qgen.DB_Table_Column{"uid","int",0,false,false,""}, qgen.DB_Table_Column{"uid","int",0,false,false,""},
@ -175,13 +177,22 @@ func create_tables(adapter qgen.DB_Adapter) error {
}, },
) )
qgen.Install.CreateTable("sync", "", "",
[]qgen.DB_Table_Column{
qgen.DB_Table_Column{"last_update", "datetime", 0, false, false, ""},
},
[]qgen.DB_Table_Key{},
)
return nil return nil
} }
// nolint
func seed_tables(adapter qgen.DB_Adapter) error { func seed_tables(adapter qgen.DB_Adapter) error {
return nil return nil
} }
// nolint
func write_selects(adapter qgen.DB_Adapter) error { func write_selects(adapter qgen.DB_Adapter) error {
// url_prefix and url_name will be removed from this query in a later commit // url_prefix and url_name will be removed from this query in a later commit
adapter.SimpleSelect("get_user", "users", "name, group, is_super_admin, avatar, message, url_prefix, url_name, level", "uid = ?", "", "") adapter.SimpleSelect("get_user", "users", "name, group, is_super_admin, avatar, message, url_prefix, url_name, level", "uid = ?", "", "")
@ -194,7 +205,6 @@ func write_selects(adapter qgen.DB_Adapter) error {
adapter.SimpleSelect("get_password", "users", "password,salt", "uid = ?", "", "") adapter.SimpleSelect("get_password", "users", "password,salt", "uid = ?", "", "")
adapter.SimpleSelect("get_settings", "settings", "name, content, type", "", "", "") adapter.SimpleSelect("get_settings", "settings", "name, content, type", "", "", "")
adapter.SimpleSelect("get_setting", "settings", "content, type", "name = ?", "", "") adapter.SimpleSelect("get_setting", "settings", "content, type", "name = ?", "", "")
@ -259,9 +269,12 @@ func write_selects(adapter qgen.DB_Adapter) error {
adapter.SimpleSelect("get_expired_scheduled_groups", "users_groups_scheduler", "uid", "UTC_TIMESTAMP() > revert_at AND temporary = 1", "", "") adapter.SimpleSelect("get_expired_scheduled_groups", "users_groups_scheduler", "uid", "UTC_TIMESTAMP() > revert_at AND temporary = 1", "", "")
adapter.SimpleSelect("get_sync", "sync", "last_update", "", "", "")
return nil return nil
} }
// nolint
func write_left_joins(adapter qgen.DB_Adapter) error { func write_left_joins(adapter qgen.DB_Adapter) error {
adapter.SimpleLeftJoin("get_topic_replies_offset", "replies", "users", "replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.group, users.url_prefix, users.url_name, users.level, replies.ipaddress, replies.likeCount, replies.actionType", "replies.createdBy = users.uid", "tid = ?", "", "?,?") adapter.SimpleLeftJoin("get_topic_replies_offset", "replies", "users", "replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.group, users.url_prefix, users.url_name, users.level, replies.ipaddress, replies.likeCount, replies.actionType", "replies.createdBy = users.uid", "tid = ?", "", "?,?")
@ -280,12 +293,14 @@ func write_left_joins(adapter qgen.DB_Adapter) error {
return nil return nil
} }
// nolint
func write_inner_joins(adapter qgen.DB_Adapter) error { func write_inner_joins(adapter qgen.DB_Adapter) error {
adapter.SimpleInnerJoin("get_watchers", "activity_stream", "activity_subscriptions", "activity_subscriptions.user", "activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor", "asid = ?", "", "") adapter.SimpleInnerJoin("get_watchers", "activity_stream", "activity_subscriptions", "activity_subscriptions.user", "activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor", "asid = ?", "", "")
return nil return nil
} }
// nolint
func write_inserts(adapter qgen.DB_Adapter) error { func write_inserts(adapter qgen.DB_Adapter) error {
adapter.SimpleInsert("create_topic", "topics", "parentID,title,content,parsed_content,createdAt,lastReplyAt,lastReplyBy,ipaddress,words,createdBy", "?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,?") adapter.SimpleInsert("create_topic", "topics", "parentID,title,content,parsed_content,createdAt,lastReplyAt,lastReplyBy,ipaddress,words,createdBy", "?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,?")
@ -315,7 +330,6 @@ func write_inserts(adapter qgen.DB_Adapter) error {
adapter.SimpleInsert("add_theme", "themes", "uname,default", "?,?") adapter.SimpleInsert("add_theme", "themes", "uname,default", "?,?")
adapter.SimpleInsert("create_group", "users_groups", "name, tag, is_admin, is_mod, is_banned, permissions", "?,?,?,?,?,?") adapter.SimpleInsert("create_group", "users_groups", "name, tag, is_admin, is_mod, is_banned, permissions", "?,?,?,?,?,?")
adapter.SimpleInsert("add_modlog_entry", "moderation_logs", "action, elementID, elementType, ipaddress, actorID, doneAt", "?,?,?,?,?,UTC_TIMESTAMP()") adapter.SimpleInsert("add_modlog_entry", "moderation_logs", "action, elementID, elementType, ipaddress, actorID, doneAt", "?,?,?,?,?,UTC_TIMESTAMP()")
@ -327,6 +341,7 @@ func write_inserts(adapter qgen.DB_Adapter) error {
return nil return nil
} }
// nolint
func write_replaces(adapter qgen.DB_Adapter) error { func write_replaces(adapter qgen.DB_Adapter) error {
adapter.SimpleReplace("add_forum_perms_to_group", "forums_permissions", "gid,fid,preset,permissions", "?,?,?,?") adapter.SimpleReplace("add_forum_perms_to_group", "forums_permissions", "gid,fid,preset,permissions", "?,?,?,?")
@ -335,6 +350,7 @@ func write_replaces(adapter qgen.DB_Adapter) error {
return nil return nil
} }
// nolint
func write_updates(adapter qgen.DB_Adapter) error { func write_updates(adapter qgen.DB_Adapter) error {
adapter.SimpleUpdate("add_replies_to_topic", "topics", "postCount = postCount + ?, lastReplyBy = ?, lastReplyAt = UTC_TIMESTAMP()", "tid = ?") adapter.SimpleUpdate("add_replies_to_topic", "topics", "postCount = postCount + ?, lastReplyBy = ?, lastReplyAt = UTC_TIMESTAMP()", "tid = ?")
@ -386,9 +402,6 @@ func write_updates(adapter qgen.DB_Adapter) error {
adapter.SimpleUpdate("edit_profile_reply", "users_replies", "content = ?, parsed_content = ?", "rid = ?") adapter.SimpleUpdate("edit_profile_reply", "users_replies", "content = ?, parsed_content = ?", "rid = ?")
//delete_forum_stmt, err = db.Prepare("delete from forums where fid = ?")
adapter.SimpleUpdate("delete_forum","forums","name= '', active = 0","fid = ?")
adapter.SimpleUpdate("update_forum", "forums", "name = ?, desc = ?, active = ?, preset = ?", "fid = ?") adapter.SimpleUpdate("update_forum", "forums", "name = ?, desc = ?, active = ?, preset = ?", "fid = ?")
adapter.SimpleUpdate("update_setting", "settings", "content = ?", "name = ?") adapter.SimpleUpdate("update_setting", "settings", "content = ?", "name = ?")
@ -415,15 +428,17 @@ func write_updates(adapter qgen.DB_Adapter) error {
adapter.SimpleUpdate("update_word_filter", "word_filters", "find = ?, replacement = ?", "wfid = ?") adapter.SimpleUpdate("update_word_filter", "word_filters", "find = ?, replacement = ?", "wfid = ?")
adapter.SimpleUpdate("bump_sync", "sync", "last_update = UTC_TIMESTAMP()", "")
return nil return nil
} }
// nolint
func write_deletes(adapter qgen.DB_Adapter) error { func write_deletes(adapter qgen.DB_Adapter) error {
adapter.SimpleDelete("delete_reply", "replies", "rid = ?") adapter.SimpleDelete("delete_reply", "replies", "rid = ?")
adapter.SimpleDelete("delete_topic", "topics", "tid = ?") adapter.SimpleDelete("delete_topic", "topics", "tid = ?")
adapter.SimpleDelete("delete_profile_reply", "users_replies", "rid = ?") adapter.SimpleDelete("delete_profile_reply", "users_replies", "rid = ?")
adapter.SimpleDelete("delete_forum_perms_by_forum", "forums_permissions", "fid = ?") adapter.SimpleDelete("delete_forum_perms_by_forum", "forums_permissions", "fid = ?")
@ -436,6 +451,7 @@ func write_deletes(adapter qgen.DB_Adapter) error {
return nil return nil
} }
// nolint
func write_simple_counts(adapter qgen.DB_Adapter) error { func write_simple_counts(adapter qgen.DB_Adapter) error {
adapter.SimpleCount("report_exists", "topics", "data = ? AND data != '' AND parentID = 1", "") adapter.SimpleCount("report_exists", "topics", "data = ? AND data != '' AND parentID = 1", "")
@ -446,6 +462,7 @@ func write_simple_counts(adapter qgen.DB_Adapter) error {
return nil return nil
} }
// nolint
func write_insert_selects(adapter qgen.DB_Adapter) error { func write_insert_selects(adapter qgen.DB_Adapter) error {
adapter.SimpleInsertSelect("add_forum_perms_to_forum_admins", adapter.SimpleInsertSelect("add_forum_perms_to_forum_admins",
qgen.DB_Insert{"forums_permissions", "gid,fid,preset,permissions", ""}, qgen.DB_Insert{"forums_permissions", "gid,fid,preset,permissions", ""},
@ -465,10 +482,12 @@ func write_insert_selects(adapter qgen.DB_Adapter) error {
return nil return nil
} }
// nolint
func write_insert_left_joins(adapter qgen.DB_Adapter) error { func write_insert_left_joins(adapter qgen.DB_Adapter) error {
return nil return nil
} }
// nolint
func write_insert_inner_joins(adapter qgen.DB_Adapter) error { func write_insert_inner_joins(adapter qgen.DB_Adapter) error {
adapter.SimpleInsertInnerJoin("notify_watchers", adapter.SimpleInsertInnerJoin("notify_watchers",
qgen.DB_Insert{"activity_stream_matches", "watcher, asid", ""}, qgen.DB_Insert{"activity_stream_matches", "watcher, asid", ""},

View File

@ -6,30 +6,35 @@ import "strings"
import "sync" import "sync"
import "net/http" import "net/http"
// TO-DO: Support the new handler signatures created by our efforts to move the PreRoute middleware into the generated router // TODO: Support the new handler signatures created by our efforts to move the PreRoute middleware into the generated router
// nolint Stop linting the uselessness of this file, we never know when we might need this file again
type Router struct { type Router struct {
sync.RWMutex sync.RWMutex
routes map[string]func(http.ResponseWriter, *http.Request) routes map[string]func(http.ResponseWriter, *http.Request)
} }
// nolint
func NewRouter() *Router { func NewRouter() *Router {
return &Router{ return &Router{
routes: make(map[string]func(http.ResponseWriter, *http.Request)), routes: make(map[string]func(http.ResponseWriter, *http.Request)),
} }
} }
// nolint
func (router *Router) Handle(pattern string, handle http.Handler) { func (router *Router) Handle(pattern string, handle http.Handler) {
router.Lock() router.Lock()
router.routes[pattern] = handle.ServeHTTP router.routes[pattern] = handle.ServeHTTP
router.Unlock() router.Unlock()
} }
// nolint
func (router *Router) HandleFunc(pattern string, handle func(http.ResponseWriter, *http.Request)) { func (router *Router) HandleFunc(pattern string, handle func(http.ResponseWriter, *http.Request)) {
router.Lock() router.Lock()
router.routes[pattern] = handle router.routes[pattern] = handle
router.Unlock() router.Unlock()
} }
// nolint
func (router *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (router *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' { if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' {
w.WriteHeader(405) w.WriteHeader(405)
@ -37,9 +42,9 @@ func (router *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
var /*extra_data, */prefix string var /*extraData, */ prefix string
if req.URL.Path[len(req.URL.Path)-1] != '/' { if req.URL.Path[len(req.URL.Path)-1] != '/' {
//extra_data = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:] //extraData = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:]
prefix = req.URL.Path[:strings.LastIndexByte(req.URL.Path, '/')+1] prefix = req.URL.Path[:strings.LastIndexByte(req.URL.Path, '/')+1]
} else { } else {
prefix = req.URL.Path prefix = req.URL.Path

View File

@ -2,6 +2,7 @@
package main package main
import "log" import "log"
//import "strings" //import "strings"
import "os" import "os"
@ -15,7 +16,7 @@ func main() {
routes() routes()
var out string var out string
var fdata string = "// Code generated by. DO NOT EDIT.\n/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */\n" var fdata = "// Code generated by. DO NOT EDIT.\n/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */\n"
for _, route := range route_list { for _, route := range route_list {
var end int var end int
@ -145,6 +146,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
log.Print("prefix: ", prefix) log.Print("prefix: ", prefix)
log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.Path: ", req.URL.Path)
log.Print("extra_data: ", extra_data) log.Print("extra_data: ", extra_data)
log.Print("req.Referer(): ", req.Referer())
} }
if prefix == "/static" { if prefix == "/static" {
@ -178,7 +180,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
case "": case "":
// Stop the favicons, robots.txt file, etc. resolving to the topics list // Stop the favicons, robots.txt file, etc. resolving to the topics list
// TO-DO: Add support for favicons and robots.txt files // TODO: Add support for favicons and robots.txt files
switch(extra_data) { switch(extra_data) {
case "robots.txt": case "robots.txt":
route_robots_txt(w,req) route_robots_txt(w,req)

View File

@ -30,6 +30,7 @@ func routes() {
addRoute("route_forum", "/forum/", "", "extra_data") addRoute("route_forum", "/forum/", "", "extra_data")
//addRoute("route_topic_create","/topics/create/","","extra_data") //addRoute("route_topic_create","/topics/create/","","extra_data")
//addRoute("route_topics","/topics/",""/*,"&groups","&forums"*/) //addRoute("route_topics","/topics/",""/*,"&groups","&forums"*/)
addRoute("route_change_theme", "/theme/", "")
addRouteGroup("/report/", addRouteGroup("/report/",
Route{"route_report_submit", "/report/submit/", "", []string{"extra_data"}}, Route{"route_report_submit", "/report/submit/", "", []string{"extra_data"}},

168
routes.go
View File

@ -35,10 +35,10 @@ func init() {
hvars = &HeaderVars{Site: site} hvars = &HeaderVars{Site: site}
} }
type HttpsRedirect struct { type HTTPSRedirect struct {
} }
func (red *HttpsRedirect) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (red *HTTPSRedirect) ServeHTTP(w http.ResponseWriter, req *http.Request) {
dest := "https://" + req.Host + req.URL.Path dest := "https://" + req.Host + req.URL.Path
if len(req.URL.RawQuery) > 0 { if len(req.URL.RawQuery) > 0 {
dest += "?" + req.URL.RawQuery dest += "?" + req.URL.RawQuery
@ -54,13 +54,14 @@ func route_static(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
return return
} }
h := w.Header()
// Surely, there's a more efficient way of doing this? // Surely, there's a more efficient way of doing this?
if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && file.Info.ModTime().Before(t.Add(1*time.Second)) { t, err := time.Parse(http.TimeFormat, h.Get("If-Modified-Since"))
if err == nil && file.Info.ModTime().Before(t.Add(1*time.Second)) {
w.WriteHeader(http.StatusNotModified) w.WriteHeader(http.StatusNotModified)
return return
} }
h := w.Header()
h.Set("Last-Modified", file.FormattedModTime) h.Set("Last-Modified", file.FormattedModTime)
h.Set("Content-Type", file.Mimetype) h.Set("Content-Type", file.Mimetype)
//Cache-Control: max-age=31536000 //Cache-Control: max-age=31536000
@ -68,7 +69,7 @@ func route_static(w http.ResponseWriter, r *http.Request) {
h.Set("Vary", "Accept-Encoding") h.Set("Vary", "Accept-Encoding")
//http.ServeContent(w,r,r.URL.Path,file.Info.ModTime(),file) //http.ServeContent(w,r,r.URL.Path,file.Info.ModTime(),file)
//w.Write(file.Data) //w.Write(file.Data)
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { if strings.Contains(h.Get("Accept-Encoding"), "gzip") {
h.Set("Content-Encoding", "gzip") h.Set("Content-Encoding", "gzip")
h.Set("Content-Length", strconv.FormatInt(file.GzipLength, 10)) h.Set("Content-Length", strconv.FormatInt(file.GzipLength, 10))
io.Copy(w, bytes.NewReader(file.GzipData)) // Use w.Write instead? io.Copy(w, bytes.NewReader(file.GzipData)) // Use w.Write instead?
@ -90,9 +91,9 @@ func route_fstatic(w http.ResponseWriter, r *http.Request){
http.ServeFile(w,r,r.URL.Path) http.ServeFile(w,r,r.URL.Path)
}*/ }*/
// TO-DO: Make this a static file somehow? Is it possible for us to put this file somewhere else? // TODO: Make this a static file somehow? Is it possible for us to put this file somewhere else?
// TO-DO: Add a sitemap // TODO: Add a sitemap
// TO-DO: Add an API so that plugins can register disallowed areas. E.g. /groups/join for plugin_socialgroups // TODO: Add an API so that plugins can register disallowed areas. E.g. /groups/join for plugin_socialgroups
func route_robots_txt(w http.ResponseWriter, r *http.Request) { func route_robots_txt(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(`User-agent: * _, _ = w.Write([]byte(`User-agent: *
Disallow: /panel/ Disallow: /panel/
@ -148,7 +149,7 @@ func route_custom_page(w http.ResponseWriter, r *http.Request, user User) {
} }
} }
// TO-DO: Paginate this // TODO: Paginate this
func route_topics(w http.ResponseWriter, r *http.Request, user User) { func route_topics(w http.ResponseWriter, r *http.Request, user User) {
headerVars, ok := SessionCheck(w, r, &user) headerVars, ok := SessionCheck(w, r, &user)
if !ok { if !ok {
@ -232,7 +233,7 @@ func route_topics(w http.ResponseWriter, r *http.Request, user User) {
i++ i++
} }
// TO-DO: What if a user is deleted via the Control Panel? // TODO: What if a user is deleted via the Control Panel?
userList, err := users.BulkCascadeGetMap(idSlice) userList, err := users.BulkCascadeGetMap(idSlice)
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
@ -240,7 +241,7 @@ func route_topics(w http.ResponseWriter, r *http.Request, user User) {
} }
// Second pass to the add the user data // Second pass to the add the user data
// TO-DO: Use a pointer to TopicsRow instead of TopicsRow itself? // TODO: Use a pointer to TopicsRow instead of TopicsRow itself?
for _, topicItem := range topicList { for _, topicItem := range topicList {
topicItem.Creator = userList[topicItem.CreatedBy] topicItem.Creator = userList[topicItem.CreatedBy]
topicItem.LastUser = userList[topicItem.LastReplyBy] topicItem.LastUser = userList[topicItem.LastReplyBy]
@ -279,7 +280,7 @@ func route_forum(w http.ResponseWriter, r *http.Request, user User, sfid string)
return return
} }
// TO-DO: Fix this double-check // TODO: Fix this double-check
forum, err := fstore.CascadeGet(fid) forum, err := fstore.CascadeGet(fid)
if err == ErrNoRows { if err == ErrNoRows {
NotFound(w, r) NotFound(w, r)
@ -309,7 +310,7 @@ func route_forum(w http.ResponseWriter, r *http.Request, user User, sfid string)
} }
defer rows.Close() defer rows.Close()
// TO-DO: Use something other than TopicsRow as we don't need to store the forum name and link on each and every topic item? // TODO: Use something other than TopicsRow as we don't need to store the forum name and link on each and every topic item?
var topicList []*TopicsRow var topicList []*TopicsRow
var reqUserList = make(map[int]bool) var reqUserList = make(map[int]bool)
for rows.Next() { for rows.Next() {
@ -347,7 +348,7 @@ func route_forum(w http.ResponseWriter, r *http.Request, user User, sfid string)
i++ i++
} }
// TO-DO: What if a user is deleted via the Control Panel? // TODO: What if a user is deleted via the Control Panel?
userList, err := users.BulkCascadeGetMap(idSlice) userList, err := users.BulkCascadeGetMap(idSlice)
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
@ -355,7 +356,7 @@ func route_forum(w http.ResponseWriter, r *http.Request, user User, sfid string)
} }
// Second pass to the add the user data // Second pass to the add the user data
// TO-DO: Use a pointer to TopicsRow instead of TopicsRow itself? // TODO: Use a pointer to TopicsRow instead of TopicsRow itself?
for _, topicItem := range topicList { for _, topicItem := range topicList {
topicItem.Creator = userList[topicItem.CreatedBy] topicItem.Creator = userList[topicItem.CreatedBy]
topicItem.LastUser = userList[topicItem.LastReplyBy] topicItem.LastUser = userList[topicItem.LastReplyBy]
@ -381,7 +382,7 @@ func route_forums(w http.ResponseWriter, r *http.Request, user User) {
var forumList []Forum var forumList []Forum
var canSee []int var canSee []int
if user.IsSuperAdmin { if user.IsSuperAdmin {
canSee, err = fstore.GetAllIDs() canSee, err = fstore.GetAllVisibleIDs()
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
return return
@ -396,7 +397,7 @@ func route_forums(w http.ResponseWriter, r *http.Request, user User) {
for _, fid := range canSee { for _, fid := range canSee {
//log.Print(forums[fid]) //log.Print(forums[fid])
var forum = *fstore.DirtyGet(fid) var forum = *fstore.DirtyGet(fid)
if forum.Active && forum.Name != "" && forum.ParentID == 0 { if forum.ParentID == 0 {
if forum.LastTopicID != 0 { if forum.LastTopicID != 0 {
forum.LastTopicTime, err = relativeTime(forum.LastTopicTime) forum.LastTopicTime, err = relativeTime(forum.LastTopicTime)
if err != nil { if err != nil {
@ -582,7 +583,7 @@ func route_topic_id(w http.ResponseWriter, r *http.Request, user User) {
} }
replyItem.Liked = false replyItem.Liked = false
// TO-DO: Rename this to topic_rrow_assign // TODO: Rename this to topic_rrow_assign
if hooks["rrow_assign"] != nil { if hooks["rrow_assign"] != nil {
runHook("rrow_assign", &replyItem) runHook("rrow_assign", &replyItem)
} }
@ -682,7 +683,7 @@ func route_profile(w http.ResponseWriter, r *http.Request, user User) {
replyLiked := false replyLiked := false
replyLikeCount := 0 replyLikeCount := 0
// TO-DO: Add a hook here // TODO: Add a hook here
replyList = append(replyList, Reply{rid, puser.ID, replyContent, parseMessage(replyContent), replyCreatedBy, buildProfileURL(nameToSlug(replyCreatedByName), replyCreatedBy), replyCreatedByName, replyGroup, replyCreatedAt, replyLastEdit, replyLastEditBy, replyAvatar, replyClassName, replyLines, replyTag, "", "", "", 0, "", replyLiked, replyLikeCount, "", ""}) replyList = append(replyList, Reply{rid, puser.ID, replyContent, parseMessage(replyContent), replyCreatedBy, buildProfileURL(nameToSlug(replyCreatedByName), replyCreatedBy), replyCreatedByName, replyGroup, replyCreatedAt, replyLastEdit, replyLastEditBy, replyAvatar, replyClassName, replyLines, replyTag, "", "", "", 0, "", replyLiked, replyLikeCount, "", ""})
} }
@ -731,10 +732,11 @@ func route_topic_create(w http.ResponseWriter, r *http.Request, user User, sfid
runVhook("topic_create_pre_loop", w, r, fid, &headerVars, &user, &strictmode) runVhook("topic_create_pre_loop", w, r, fid, &headerVars, &user, &strictmode)
} }
// TODO: Re-add support for plugin_socialgroups
var forumList []Forum var forumList []Forum
var canSee []int var canSee []int
if user.IsSuperAdmin { if user.IsSuperAdmin {
canSee, err = fstore.GetAllIDs() canSee, err = fstore.GetAllVisibleIDs()
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
return return
@ -744,26 +746,24 @@ func route_topic_create(w http.ResponseWriter, r *http.Request, user User, sfid
canSee = group.CanSee canSee = group.CanSee
} }
// TO-DO: plugin_superadmin needs to be able to override this loop. Skip flag on topic_create_pre_loop? // TODO: plugin_superadmin needs to be able to override this loop. Skip flag on topic_create_pre_loop?
for _, ffid := range canSee { for _, ffid := range canSee {
// TO-DO: Surely, there's a better way of doing this. I've added it in for now to support plugin_socialgroups, but we really need to clean this up // TODO: Surely, there's a better way of doing this. I've added it in for now to support plugin_socialgroups, but we really need to clean this up
if strictmode && ffid != fid { if strictmode && ffid != fid {
continue continue
} }
// 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 := fstore.DirtyGet(ffid) forum := fstore.DirtyGet(ffid)
if forum.Active && forum.Name != "" {
fcopy := *forum fcopy := *forum
if hooks["topic_create_frow_assign"] != nil { if hooks["topic_create_frow_assign"] != nil {
// TO-DO: Add the skip feature to all the other row based hooks? // TODO: Add the skip feature to all the other row based hooks?
if runHook("topic_create_frow_assign", &fcopy).(bool) { if runHook("topic_create_frow_assign", &fcopy).(bool) {
continue continue
} }
} }
forumList = append(forumList, fcopy) forumList = append(forumList, fcopy)
} }
}
ctpage := CreateTopicPage{"Create Topic", user, headerVars, forumList, fid} ctpage := CreateTopicPage{"Create Topic", user, headerVars, forumList, fid}
if preRenderHooks["pre_render_create_topic"] != nil { if preRenderHooks["pre_render_create_topic"] != nil {
@ -789,7 +789,7 @@ func route_topic_create_submit(w http.ResponseWriter, r *http.Request, user User
return return
} }
// TO-DO: Add hooks to make use of headerLite // TODO: Add hooks to make use of headerLite
_, ok := SimpleForumSessionCheck(w, r, &user, fid) _, ok := SimpleForumSessionCheck(w, r, &user, fid)
if !ok { if !ok {
return return
@ -799,7 +799,7 @@ func route_topic_create_submit(w http.ResponseWriter, r *http.Request, user User
return return
} }
topic_name := html.EscapeString(r.PostFormValue("topic-name")) topicName := html.EscapeString(r.PostFormValue("topic-name"))
content := html.EscapeString(preparseMessage(r.PostFormValue("topic-content"))) content := html.EscapeString(preparseMessage(r.PostFormValue("topic-content")))
ipaddress, _, err := net.SplitHostPort(r.RemoteAddr) ipaddress, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil { if err != nil {
@ -808,7 +808,7 @@ func route_topic_create_submit(w http.ResponseWriter, r *http.Request, user User
} }
wcount := wordCount(content) wcount := wordCount(content)
res, err := create_topic_stmt.Exec(fid, topic_name, content, parseMessage(content), user.ID, ipaddress, wcount, user.ID) res, err := create_topic_stmt.Exec(fid, topicName, content, parseMessage(content), user.ID, ipaddress, wcount, user.ID)
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
return return
@ -832,13 +832,13 @@ func route_topic_create_submit(w http.ResponseWriter, r *http.Request, user User
} }
http.Redirect(w, r, "/topic/"+strconv.FormatInt(lastID, 10), http.StatusSeeOther) http.Redirect(w, r, "/topic/"+strconv.FormatInt(lastID, 10), http.StatusSeeOther)
err = increase_post_user_stats(wcount, user.ID, true, user) err = user.increasePostStats(wcount, true)
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
return return
} }
err = fstore.UpdateLastTopic(topic_name, int(lastID), user.Name, user.ID, time.Now().Format("2006-01-02 15:04:05"), fid) err = fstore.UpdateLastTopic(topicName, int(lastID), user.Name, user.ID, time.Now().Format("2006-01-02 15:04:05"), fid)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
InternalError(err, w) InternalError(err, w)
} }
@ -865,7 +865,7 @@ func route_create_reply(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
// TO-DO: Add hooks to make use of headerLite // TODO: Add hooks to make use of headerLite
_, ok := SimpleForumSessionCheck(w, r, &user, topic.ParentID) _, ok := SimpleForumSessionCheck(w, r, &user, topic.ParentID)
if !ok { if !ok {
return return
@ -933,7 +933,7 @@ func route_create_reply(w http.ResponseWriter, r *http.Request, user User) {
} }
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther) http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
err = increase_post_user_stats(wcount, user.ID, false, user) err = user.increasePostStats(wcount, false)
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
return return
@ -962,7 +962,7 @@ func route_like_topic(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
// TO-DO: Add hooks to make use of headerLite // TODO: Add hooks to make use of headerLite
_, ok := SimpleForumSessionCheck(w, r, &user, topic.ParentID) _, ok := SimpleForumSessionCheck(w, r, &user, topic.ParentID)
if !ok { if !ok {
return return
@ -1073,7 +1073,7 @@ func route_reply_like_submit(w http.ResponseWriter, r *http.Request, user User)
return return
} }
// TO-DO: Add hooks to make use of headerLite // TODO: Add hooks to make use of headerLite
_, ok := SimpleForumSessionCheck(w, r, &user, fid) _, ok := SimpleForumSessionCheck(w, r, &user, fid)
if !ok { if !ok {
return return
@ -1171,8 +1171,8 @@ func route_profile_reply_create(w http.ResponseWriter, r *http.Request, user Use
return return
} }
var user_name string var userName string
err = get_user_name_stmt.QueryRow(uid).Scan(&user_name) err = get_user_name_stmt.QueryRow(uid).Scan(&userName)
if err == ErrNoRows { if err == ErrNoRows {
LocalError("The profile you're trying to post on doesn't exist.", w, r, user) LocalError("The profile you're trying to post on doesn't exist.", w, r, user)
return return
@ -1184,7 +1184,7 @@ func route_profile_reply_create(w http.ResponseWriter, r *http.Request, user Use
http.Redirect(w, r, "/user/"+strconv.Itoa(uid), http.StatusSeeOther) http.Redirect(w, r, "/user/"+strconv.Itoa(uid), http.StatusSeeOther)
} }
func route_report_submit(w http.ResponseWriter, r *http.Request, user User, sitem_id string) { func route_report_submit(w http.ResponseWriter, r *http.Request, user User, sitemID string) {
if !user.Loggedin { if !user.Loggedin {
LoginRequired(w, r, user) LoginRequired(w, r, user)
return return
@ -1204,18 +1204,18 @@ func route_report_submit(w http.ResponseWriter, r *http.Request, user User, site
return return
} }
item_id, err := strconv.Atoi(sitem_id) itemID, err := strconv.Atoi(sitemID)
if err != nil { if err != nil {
LocalError("Bad ID", w, r, user) LocalError("Bad ID", w, r, user)
return return
} }
item_type := r.FormValue("type") itemType := r.FormValue("type")
var fid int = 1 var fid = 1
var title, content string var title, content string
if item_type == "reply" { if itemType == "reply" {
reply, err := getReply(item_id) reply, err := getReply(itemID)
if err == ErrNoRows { if err == ErrNoRows {
LocalError("We were unable to find the reported post", w, r, user) LocalError("We were unable to find the reported post", w, r, user)
return return
@ -1234,9 +1234,9 @@ func route_report_submit(w http.ResponseWriter, r *http.Request, user User, site
} }
title = "Reply: " + topic.Title title = "Reply: " + topic.Title
content = reply.Content + "\n\nOriginal Post: #rid-" + strconv.Itoa(item_id) content = reply.Content + "\n\nOriginal Post: #rid-" + strconv.Itoa(itemID)
} else if item_type == "user-reply" { } else if itemType == "user-reply" {
userReply, err := getUserReply(item_id) userReply, err := getUserReply(itemID)
if err == ErrNoRows { if err == ErrNoRows {
LocalError("We weren't able to find the reported post", w, r, user) LocalError("We weren't able to find the reported post", w, r, user)
return return
@ -1255,8 +1255,8 @@ func route_report_submit(w http.ResponseWriter, r *http.Request, user User, site
} }
title = "Profile: " + title title = "Profile: " + title
content = userReply.Content + "\n\nOriginal Post: @" + strconv.Itoa(userReply.ParentID) content = userReply.Content + "\n\nOriginal Post: @" + strconv.Itoa(userReply.ParentID)
} else if item_type == "topic" { } else if itemType == "topic" {
err = get_topic_basic_stmt.QueryRow(item_id).Scan(&title, &content) err = get_topic_basic_stmt.QueryRow(itemID).Scan(&title, &content)
if err == ErrNoRows { if err == ErrNoRows {
NotFound(w, r) NotFound(w, r)
return return
@ -1265,10 +1265,10 @@ func route_report_submit(w http.ResponseWriter, r *http.Request, user User, site
return return
} }
title = "Topic: " + title title = "Topic: " + title
content = content + "\n\nOriginal Post: #tid-" + strconv.Itoa(item_id) content = content + "\n\nOriginal Post: #tid-" + strconv.Itoa(itemID)
} else { } else {
if vhooks["report_preassign"] != nil { if vhooks["report_preassign"] != nil {
runVhookNoreturn("report_preassign", &item_id, &item_type) runVhookNoreturn("report_preassign", &itemID, &itemType)
return return
} }
// Don't try to guess the type // Don't try to guess the type
@ -1277,7 +1277,7 @@ func route_report_submit(w http.ResponseWriter, r *http.Request, user User, site
} }
var count int var count int
rows, err := report_exists_stmt.Query(item_type + "_" + strconv.Itoa(item_id)) rows, err := report_exists_stmt.Query(itemType + "_" + strconv.Itoa(itemID))
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
InternalError(err, w) InternalError(err, w)
return return
@ -1295,7 +1295,7 @@ func route_report_submit(w http.ResponseWriter, r *http.Request, user User, site
return return
} }
res, err := create_report_stmt.Exec(title, content, parseMessage(content), user.ID, item_type+"_"+strconv.Itoa(item_id)) res, err := create_report_stmt.Exec(title, content, parseMessage(content), user.ID, itemType+"_"+strconv.Itoa(itemID))
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
return return
@ -1546,14 +1546,15 @@ func route_account_own_edit_username_submit(w http.ResponseWriter, r *http.Reque
return return
} }
new_username := html.EscapeString(r.PostFormValue("account-new-username")) newUsername := html.EscapeString(r.PostFormValue("account-new-username"))
_, err = set_username_stmt.Exec(new_username, strconv.Itoa(user.ID)) _, err = set_username_stmt.Exec(newUsername, strconv.Itoa(user.ID))
if err != nil { if err != nil {
LocalError("Unable to change the username. Does someone else already have this name?", w, r, user) LocalError("Unable to change the username. Does someone else already have this name?", w, r, user)
return return
} }
user.Name = new_username // TODO: Use the reloaded data instead for the name?
user.Name = newUsername
err = users.Load(user.ID) err = users.Load(user.ID)
if err != nil { if err != nil {
LocalError("Your account doesn't exist!", w, r, user) LocalError("Your account doesn't exist!", w, r, user)
@ -1704,6 +1705,7 @@ func route_account_own_edit_email_token_submit(w http.ResponseWriter, r *http.Re
templates.ExecuteTemplate(w, "account-own-edit-email.html", pi) templates.ExecuteTemplate(w, "account-own-edit-email.html", pi)
} }
// TODO: Move this into member_routes.go
func route_logout(w http.ResponseWriter, r *http.Request, user User) { func route_logout(w http.ResponseWriter, r *http.Request, user User) {
if !user.Loggedin { if !user.Loggedin {
LocalError("You can't logout without logging in first.", w, r, user) LocalError("You can't logout without logging in first.", w, r, user)
@ -1731,9 +1733,9 @@ func route_login(w http.ResponseWriter, r *http.Request, user User) {
templates.ExecuteTemplate(w, "login.html", pi) templates.ExecuteTemplate(w, "login.html", pi)
} }
// TO-DO: Log failed attempted logins? // TODO: Log failed attempted logins?
// TO-DO: Lock IPS out if they have too many failed attempts? // TODO: Lock IPS out if they have too many failed attempts?
// TO-DO: Log unusual countries in comparison to the country a user usually logs in from? Alert the user about this? // TODO: Log unusual countries in comparison to the country a user usually logs in from? Alert the user about this?
func route_login_submit(w http.ResponseWriter, r *http.Request, user User) { func route_login_submit(w http.ResponseWriter, r *http.Request, user User) {
if user.Loggedin { if user.Loggedin {
LocalError("You're already logged in.", w, r, user) LocalError("You're already logged in.", w, r, user)
@ -1842,11 +1844,11 @@ func route_register_submit(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
confirm_password := r.PostFormValue("confirm_password") confirmPassword := r.PostFormValue("confirm_password")
log.Print("Registration Attempt! Username: " + username) log.Print("Registration Attempt! Username: " + username) // TODO: Add controls over what is logged when?
// Do the two inputted passwords match..? // Do the two inputted passwords match..?
if password != confirm_password { if password != confirmPassword {
LocalError("The two passwords don't match.", w, r, user) LocalError("The two passwords don't match.", w, r, user)
return return
} }
@ -1898,8 +1900,48 @@ func route_register_submit(w http.ResponseWriter, r *http.Request, user User) {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
} }
// TO-DO: We don't need support XML here to support sitemaps, we could handle those elsewhere // TODO: Set the cookie domain
var phrase_login_alerts = []byte(`{"msgs":[{"msg":"Login to see your alerts","path":"/accounts/login"}]}`) func route_change_theme(w http.ResponseWriter, r *http.Request, user User) {
//headerLite, _ := SimpleSessionCheck(w, r, &user)
err := r.ParseForm()
if err != nil {
PreError("Bad Form", w, r)
return
}
// TODO: Rename isJs to something else, just in case we rewrite the JS side in WebAssembly?
isJs := (r.PostFormValue("isJs") == "1")
newTheme := html.EscapeString(r.PostFormValue("newTheme"))
theme, ok := themes[newTheme]
if !ok || theme.HideFromThemes {
log.Print("Bad Theme: ", newTheme)
LocalErrorJSQ("That theme doesn't exist", w, r, user, isJs)
return
}
// TODO: Store the current theme in the user's account?
/*if user.Loggedin {
_, err = change_theme_stmt.Exec(newTheme, user.ID)
if err != nil {
InternalError(err, w)
return
}
}*/
cookie := http.Cookie{Name: "current_theme", Value: newTheme, Path: "/", MaxAge: year}
http.SetCookie(w, &cookie)
if !isJs {
http.Redirect(w, r, "/", http.StatusSeeOther)
} else {
_, _ = w.Write(successJSONBytes)
}
}
// TODO: We don't need support XML here to support sitemaps, we could handle those elsewhere
var phraseLoginAlerts = []byte(`{"msgs":[{"msg":"Login to see your alerts","path":"/accounts/login"}]}`)
func route_api(w http.ResponseWriter, r *http.Request, user User) { func route_api(w http.ResponseWriter, r *http.Request, user User) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
@ -1931,7 +1973,7 @@ func route_api(w http.ResponseWriter, r *http.Request, user User) {
} }
case "alerts": // A feed of events tailored for a specific user case "alerts": // A feed of events tailored for a specific user
if !user.Loggedin { if !user.Loggedin {
w.Write(phrase_login_alerts) w.Write(phraseLoginAlerts)
return return
} }

3
routes_common.go Normal file
View File

@ -0,0 +1,3 @@
package main
// TODO: Move PreRoute and it's friends here in the next commit, I'm avoiding doing it in this one as I don't want the diffs to be lost

View File

@ -0,0 +1,3 @@
CREATE TABLE `sync` (
`last_update` datetime not null
);

View File

@ -0,0 +1,3 @@
CREATE TABLE `sync` (
`last_update` timestamp not null
);

View File

@ -24,7 +24,6 @@ type Setting struct {
func init() { func init() {
settingBox.Store(SettingBox(make(map[string]interface{}))) settingBox.Store(SettingBox(make(map[string]interface{})))
//settingBox.Store(make(map[string]interface{}))
} }
func LoadSettings() error { func LoadSettings() error {
@ -34,8 +33,7 @@ func LoadSettings() error {
} }
defer rows.Close() defer rows.Close()
sBox := settingBox.Load().(SettingBox) var sBox = SettingBox(make(map[string]interface{}))
//sBox := settingBox.Load().(map[string]interface{})
var sname, scontent, stype, sconstraints string var sname, scontent, stype, sconstraints string
for rows.Next() { for rows.Next() {
err = rows.Scan(&sname, &scontent, &stype, &sconstraints) err = rows.Scan(&sname, &scontent, &stype, &sconstraints)
@ -56,10 +54,10 @@ func LoadSettings() error {
return nil return nil
} }
// TO-DO: 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 SettingBox) ParseSetting(sname string, scontent string, stype string, constraint string) string { func (sBox SettingBox) ParseSetting(sname string, scontent string, stype string, constraint string) string {
var err error var err error
var ssBox map[string]interface{} = map[string]interface{}(sBox) var ssBox = map[string]interface{}(sBox)
if stype == "bool" { if stype == "bool" {
ssBox[sname] = (scontent == "1") ssBox[sname] = (scontent == "1")
} else if stype == "int" { } else if stype == "int" {

16
site.go
View File

@ -2,13 +2,12 @@ package main
import "net/http" import "net/http"
var site *Site = &Site{Name:"Magical Fairy Land"} var site = &Site{Name: "Magical Fairy Land"}
var db_config DB_Config = DB_Config{Host:"localhost"} var db_config = DB_Config{Host: "localhost"}
var config Config var config Config
var dev DevConfig var dev DevConfig
type Site struct type Site struct {
{
Name string Name string
Email string Email string
Url string Url string
@ -18,8 +17,7 @@ type Site struct
HasProxy bool HasProxy bool
} }
type DB_Config struct type DB_Config struct {
{
Host string Host string
Username string Username string
Password string Password string
@ -27,8 +25,7 @@ type DB_Config struct
Port string Port string
} }
type Config struct type Config struct {
{
SslPrivkey string SslPrivkey string
SslFullchain string SslFullchain string
@ -54,8 +51,7 @@ type Config struct
ItemsPerPage int ItemsPerPage int
} }
type DevConfig struct type DevConfig struct {
{
DebugMode bool DebugMode bool
SuperDebug bool SuperDebug bool
Profiling bool Profiling bool

View File

@ -2,6 +2,12 @@ package main
import "time" import "time"
var lastSync time.Time
func init() {
lastSync = time.Now()
}
func handleExpiredScheduledGroups() error { func handleExpiredScheduledGroups() error {
rows, err := get_expired_scheduled_groups_stmt.Query() rows, err := get_expired_scheduled_groups_stmt.Query()
if err != nil { if err != nil {
@ -27,3 +33,37 @@ func handleExpiredScheduledGroups() error {
} }
return rows.Err() return rows.Err()
} }
func handleServerSync() error {
var lastUpdate time.Time
var lastUpdateStr string
err := get_sync_stmt.QueryRow().Scan(&lastUpdateStr)
if err != nil {
return err
}
layout := "2006-01-02 15:04:05"
lastUpdate, err = time.Parse(layout, lastUpdateStr)
if err != nil {
return err
}
if lastUpdate.After(lastSync) {
// TODO: A more granular sync
err = fstore.LoadForums()
if err != nil {
return err
}
// TODO: Resync the groups
// TODO: Resync the permissions
err = LoadSettings()
if err != nil {
return err
}
err = LoadWordFilters()
if err != nil {
return err
}
}
return nil
}

View File

@ -166,10 +166,26 @@ w.Write(forum_42)
} }
w.Write(forum_43) w.Write(forum_43)
w.Write(footer_0) w.Write(footer_0)
if tmpl_forum_vars.Header.Widgets.RightSidebar != "" { if len(tmpl_forum_vars.Header.Themes) != 0 {
for _, item := range tmpl_forum_vars.Header.Themes {
if !item.HideFromThemes {
w.Write(footer_1) w.Write(footer_1)
w.Write([]byte(string(tmpl_forum_vars.Header.Widgets.RightSidebar))) w.Write([]byte(item.Name))
w.Write(footer_2) w.Write(footer_2)
} if tmpl_forum_vars.Header.ThemeName == item.Name {
w.Write(footer_3) w.Write(footer_3)
} }
w.Write(footer_4)
w.Write([]byte(item.FriendlyName))
w.Write(footer_5)
}
}
}
w.Write(footer_6)
if tmpl_forum_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_7)
w.Write([]byte(string(tmpl_forum_vars.Header.Widgets.RightSidebar)))
w.Write(footer_8)
}
w.Write(footer_9)
}

View File

@ -109,10 +109,26 @@ w.Write(forums_17)
} }
w.Write(forums_18) w.Write(forums_18)
w.Write(footer_0) w.Write(footer_0)
if tmpl_forums_vars.Header.Widgets.RightSidebar != "" { if len(tmpl_forums_vars.Header.Themes) != 0 {
for _, item := range tmpl_forums_vars.Header.Themes {
if !item.HideFromThemes {
w.Write(footer_1) w.Write(footer_1)
w.Write([]byte(string(tmpl_forums_vars.Header.Widgets.RightSidebar))) w.Write([]byte(item.Name))
w.Write(footer_2) w.Write(footer_2)
} if tmpl_forums_vars.Header.ThemeName == item.Name {
w.Write(footer_3) w.Write(footer_3)
} }
w.Write(footer_4)
w.Write([]byte(item.FriendlyName))
w.Write(footer_5)
}
}
}
w.Write(footer_6)
if tmpl_forums_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_7)
w.Write([]byte(string(tmpl_forums_vars.Header.Widgets.RightSidebar)))
w.Write(footer_8)
}
w.Write(footer_9)
}

View File

@ -6,8 +6,9 @@ import "net/http"
var templates = template.New("") var templates = template.New("")
// nolint
func interpreted_topic_template(pi TopicPage, w http.ResponseWriter) { func interpreted_topic_template(pi TopicPage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["topic"] mapping, ok := themes[defaultThemeBox.Load().(string)].TemplatesMap["topic"]
if !ok { if !ok {
mapping = "topic" mapping = "topic"
} }
@ -17,11 +18,13 @@ func interpreted_topic_template(pi TopicPage, w http.ResponseWriter) {
} }
} }
// nolint
var template_topic_handle func(TopicPage, http.ResponseWriter) = interpreted_topic_template var template_topic_handle func(TopicPage, http.ResponseWriter) = interpreted_topic_template
var template_topic_alt_handle func(TopicPage, http.ResponseWriter) = interpreted_topic_template var template_topic_alt_handle func(TopicPage, http.ResponseWriter) = interpreted_topic_template
// nolint
var template_topics_handle func(TopicsPage, http.ResponseWriter) = func(pi TopicsPage, w http.ResponseWriter) { var template_topics_handle func(TopicsPage, http.ResponseWriter) = func(pi TopicsPage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["topics"] mapping, ok := themes[defaultThemeBox.Load().(string)].TemplatesMap["topics"]
if !ok { if !ok {
mapping = "topics" mapping = "topics"
} }
@ -31,8 +34,9 @@ var template_topics_handle func(TopicsPage, http.ResponseWriter) = func(pi Topic
} }
} }
// nolint
var template_forum_handle func(ForumPage, http.ResponseWriter) = func(pi ForumPage, w http.ResponseWriter) { var template_forum_handle func(ForumPage, http.ResponseWriter) = func(pi ForumPage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["forum"] mapping, ok := themes[defaultThemeBox.Load().(string)].TemplatesMap["forum"]
if !ok { if !ok {
mapping = "forum" mapping = "forum"
} }
@ -42,8 +46,9 @@ var template_forum_handle func(ForumPage, http.ResponseWriter) = func(pi ForumPa
} }
} }
// nolint
var template_forums_handle func(ForumsPage, http.ResponseWriter) = func(pi ForumsPage, w http.ResponseWriter) { var template_forums_handle func(ForumsPage, http.ResponseWriter) = func(pi ForumsPage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["forums"] mapping, ok := themes[defaultThemeBox.Load().(string)].TemplatesMap["forums"]
if !ok { if !ok {
mapping = "forums" mapping = "forums"
} }
@ -53,8 +58,9 @@ var template_forums_handle func(ForumsPage, http.ResponseWriter) = func(pi Forum
} }
} }
// nolint
var template_profile_handle func(ProfilePage, http.ResponseWriter) = func(pi ProfilePage, w http.ResponseWriter) { var template_profile_handle func(ProfilePage, http.ResponseWriter) = func(pi ProfilePage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["profile"] mapping, ok := themes[defaultThemeBox.Load().(string)].TemplatesMap["profile"]
if !ok { if !ok {
mapping = "profile" mapping = "profile"
} }
@ -64,8 +70,9 @@ var template_profile_handle func(ProfilePage, http.ResponseWriter) = func(pi Pro
} }
} }
// nolint
var template_create_topic_handle func(CreateTopicPage, http.ResponseWriter) = func(pi CreateTopicPage, w http.ResponseWriter) { var template_create_topic_handle func(CreateTopicPage, http.ResponseWriter) = func(pi CreateTopicPage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["create-topic"] mapping, ok := themes[defaultThemeBox.Load().(string)].TemplatesMap["create-topic"]
if !ok { if !ok {
mapping = "create-topic" mapping = "create-topic"
} }
@ -77,12 +84,18 @@ var template_create_topic_handle func(CreateTopicPage, http.ResponseWriter) = fu
func compileTemplates() error { func compileTemplates() error {
var c CTemplateSet var c CTemplateSet
// Schemas to train the template compiler on what to expect
// TODO: Add support for interface{}s
user := User{62, buildProfileURL("fake-user", 62), "Fake User", "compiler@localhost", 0, false, false, false, false, false, false, GuestPerms, make(map[string]bool), "", false, "", "", "", "", "", 0, 0, "0.0.0.0.0", 0} user := User{62, buildProfileURL("fake-user", 62), "Fake User", "compiler@localhost", 0, false, false, false, false, false, false, GuestPerms, make(map[string]bool), "", false, "", "", "", "", "", 0, 0, "0.0.0.0.0", 0}
// TO-DO: Do a more accurate level calculation for this? // TODO: Do a more accurate level calculation for this?
user2 := User{1, buildProfileURL("admin-alice", 1), "Admin Alice", "alice@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", "", "", "", "", 58, 1000, "127.0.0.1", 0} user2 := User{1, buildProfileURL("admin-alice", 1), "Admin Alice", "alice@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", "", "", "", "", 58, 1000, "127.0.0.1", 0}
user3 := User{2, buildProfileURL("admin-fred", 62), "Admin Fred", "fred@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", "", "", "", "", 42, 900, "::1", 0} user3 := User{2, buildProfileURL("admin-fred", 62), "Admin Fred", "fred@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", "", "", "", "", 42, 900, "::1", 0}
headerVars := &HeaderVars{ headerVars := &HeaderVars{
Site: site, Site: site,
Settings: settingBox.Load().(SettingBox),
Themes: themes,
ThemeName: defaultThemeBox.Load().(string),
NoticeList: []string{"test"}, NoticeList: []string{"test"},
Stylesheets: []string{"panel"}, Stylesheets: []string{"panel"},
Scripts: []string{"whatever"}, Scripts: []string{"whatever"},
@ -97,66 +110,64 @@ func compileTemplates() error {
var replyList []Reply var replyList []Reply
replyList = append(replyList, Reply{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", config.DefaultGroup, "", 0, 0, "", "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, "", ""}) replyList = append(replyList, Reply{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", config.DefaultGroup, "", 0, 0, "", "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, "", ""})
var varList map[string]VarItem = make(map[string]VarItem) var varList = make(map[string]VarItem)
tpage := TopicPage{"Title", user, headerVars, replyList, topic, 1, 1} tpage := TopicPage{"Title", user, headerVars, replyList, topic, 1, 1}
topic_id_tmpl, err := c.compileTemplate("topic.html", "templates/", "TopicPage", tpage, varList) topicIDTmpl, err := c.compileTemplate("topic.html", "templates/", "TopicPage", tpage, varList)
if err != nil { if err != nil {
return err return err
} }
topic_id_alt_tmpl, err := c.compileTemplate("topic_alt.html", "templates/", "TopicPage", tpage, varList) topicIDAltTmpl, err := c.compileTemplate("topic_alt.html", "templates/", "TopicPage", tpage, varList)
if err != nil { if err != nil {
return err return err
} }
varList = make(map[string]VarItem) varList = make(map[string]VarItem)
ppage := ProfilePage{"User 526", user, headerVars, replyList, user} ppage := ProfilePage{"User 526", user, headerVars, replyList, user}
profile_tmpl, err := c.compileTemplate("profile.html", "templates/", "ProfilePage", ppage, varList) profileTmpl, err := c.compileTemplate("profile.html", "templates/", "ProfilePage", ppage, varList)
if err != nil { if err != nil {
return err return err
} }
var forumList []Forum var forumList []Forum
forums, err := fstore.GetAll() forums, err := fstore.GetAllVisible()
if err != nil { if err != nil {
return err return err
} }
for _, forum := range forums { for _, forum := range forums {
if forum.Active {
forumList = append(forumList, *forum) forumList = append(forumList, *forum)
} }
}
varList = make(map[string]VarItem) varList = make(map[string]VarItem)
forums_page := ForumsPage{"Forum List", user, headerVars, forumList} forumsPage := ForumsPage{"Forum List", user, headerVars, forumList}
forums_tmpl, err := c.compileTemplate("forums.html", "templates/", "ForumsPage", forums_page, varList) forumsTmpl, err := c.compileTemplate("forums.html", "templates/", "ForumsPage", forumsPage, varList)
if err != nil { if err != nil {
return err return err
} }
var topicsList []*TopicsRow var topicsList []*TopicsRow
topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, "Date", "Date", user3.ID, 1, "", "127.0.0.1", 0, 1, "classname", "", &user2, "", 0, &user3, "General", "/forum/general.2"}) topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, "Date", "Date", user3.ID, 1, "", "127.0.0.1", 0, 1, "classname", "", &user2, "", 0, &user3, "General", "/forum/general.2"})
topics_page := TopicsPage{"Topic List", user, headerVars, topicsList} topicsPage := TopicsPage{"Topic List", user, headerVars, topicsList}
topics_tmpl, err := c.compileTemplate("topics.html", "templates/", "TopicsPage", topics_page, varList) topicsTmpl, err := c.compileTemplate("topics.html", "templates/", "TopicsPage", topicsPage, varList)
if err != nil { if err != nil {
return err return err
} }
//var topicList []TopicUser //var topicList []TopicUser
//topicList = append(topicList,TopicUser{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"classname","","admin-fred","Admin Fred",config.DefaultGroup,"",0,"","","","",58,false}) //topicList = append(topicList,TopicUser{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"classname","","admin-fred","Admin Fred",config.DefaultGroup,"",0,"","","","",58,false})
forum_item := Forum{1, "general", "General Forum", "Where the general stuff happens", true, "all", 0, "", 0, "", "", 0, "", 0, ""} forumItem := Forum{1, "general", "General Forum", "Where the general stuff happens", true, "all", 0, "", 0, "", "", 0, "", 0, ""}
forum_page := ForumPage{"General Forum", user, headerVars, topicsList, forum_item, 1, 1} forumPage := ForumPage{"General Forum", user, headerVars, topicsList, forumItem, 1, 1}
forum_tmpl, err := c.compileTemplate("forum.html", "templates/", "ForumPage", forum_page, varList) forumTmpl, err := c.compileTemplate("forum.html", "templates/", "ForumPage", forumPage, varList)
if err != nil { if err != nil {
return err return err
} }
log.Print("Writing the templates") log.Print("Writing the templates")
go writeTemplate("topic", topic_id_tmpl) go writeTemplate("topic", topicIDTmpl)
go writeTemplate("topic_alt", topic_id_alt_tmpl) go writeTemplate("topic_alt", topicIDAltTmpl)
go writeTemplate("profile", profile_tmpl) go writeTemplate("profile", profileTmpl)
go writeTemplate("forums", forums_tmpl) go writeTemplate("forums", forumsTmpl)
go writeTemplate("topics", topics_tmpl) go writeTemplate("topics", topicsTmpl)
go writeTemplate("forum", forum_tmpl) go writeTemplate("forum", forumTmpl)
go func() { go func() {
err := writeFile("./template_list.go", "package main\n\n// nolint\n"+c.FragOut) err := writeFile("./template_list.go", "package main\n\n// nolint\n"+c.FragOut)
if err != nil { if err != nil {
@ -180,66 +191,62 @@ func initTemplates() {
} }
compileTemplates() compileTemplates()
// TO-DO: Add support for 64-bit integers // TODO: Add support for 64-bit integers
// TO-DO: Add support for floats // TODO: Add support for floats
fmap := make(map[string]interface{}) fmap := make(map[string]interface{})
fmap["add"] = func(left interface{}, right interface{}) interface{} { fmap["add"] = func(left interface{}, right interface{}) interface{} {
var left_int int var leftInt, rightInt int
var right_int int
switch left := left.(type) { switch left := left.(type) {
case uint, uint8, uint16, int, int32: case uint, uint8, uint16, int, int32:
left_int = left.(int) leftInt = left.(int)
} }
switch right := right.(type) { switch right := right.(type) {
case uint, uint8, uint16, int, int32: case uint, uint8, uint16, int, int32:
right_int = right.(int) rightInt = right.(int)
} }
return left_int + right_int return leftInt + rightInt
} }
fmap["subtract"] = func(left interface{}, right interface{}) interface{} { fmap["subtract"] = func(left interface{}, right interface{}) interface{} {
var left_int int var leftInt, rightInt int
var right_int int
switch left := left.(type) { switch left := left.(type) {
case uint, uint8, uint16, int, int32: case uint, uint8, uint16, int, int32:
left_int = left.(int) leftInt = left.(int)
} }
switch right := right.(type) { switch right := right.(type) {
case uint, uint8, uint16, int, int32: case uint, uint8, uint16, int, int32:
right_int = right.(int) rightInt = right.(int)
} }
return left_int - right_int return leftInt - rightInt
} }
fmap["multiply"] = func(left interface{}, right interface{}) interface{} { fmap["multiply"] = func(left interface{}, right interface{}) interface{} {
var left_int int var leftInt, rightInt int
var right_int int
switch left := left.(type) { switch left := left.(type) {
case uint, uint8, uint16, int, int32: case uint, uint8, uint16, int, int32:
left_int = left.(int) leftInt = left.(int)
} }
switch right := right.(type) { switch right := right.(type) {
case uint, uint8, uint16, int, int32: case uint, uint8, uint16, int, int32:
right_int = right.(int) rightInt = right.(int)
} }
return left_int * right_int return leftInt * rightInt
} }
fmap["divide"] = func(left interface{}, right interface{}) interface{} { fmap["divide"] = func(left interface{}, right interface{}) interface{} {
var left_int int var leftInt, rightInt int
var right_int int
switch left := left.(type) { switch left := left.(type) {
case uint, uint8, uint16, int, int32: case uint, uint8, uint16, int, int32:
left_int = left.(int) leftInt = left.(int)
} }
switch right := right.(type) { switch right := right.(type) {
case uint, uint8, uint16, int, int32: case uint, uint8, uint16, int, int32:
right_int = right.(int) rightInt = right.(int)
} }
if left_int == 0 || right_int == 0 { if leftInt == 0 || rightInt == 0 {
return 0 return 0
} }
return left_int / right_int return leftInt / rightInt
} }
// The interpreted templates... // The interpreted templates...

View File

@ -245,11 +245,27 @@ var topic_91 = []byte(`
</main> </main>
`) `)
var footer_0 = []byte(` </div> var footer_0 = []byte(`<div class="footer">
<div id="poweredBy" style="float: left;margin-top: 4px;">Powered by Gosora - <span style="font-size: 12px;">Made with love by Azareal</span></div>
<form action="/theme/" method="post">
<div id="themeSelector" style="float: right;">
<select id="themeSelectorSelect" name="themeSelector" aria-label="Change the site's appearance">
`) `)
var footer_1 = []byte(`<aside class="sidebar">`) var footer_1 = []byte(`<option val="`)
var footer_2 = []byte(`</aside>`) var footer_2 = []byte(`"`)
var footer_3 = []byte(` var footer_3 = []byte(` selected`)
var footer_4 = []byte(`>`)
var footer_5 = []byte(`</option>`)
var footer_6 = []byte(`
</select>
</div>
</form>
</div>
</div>
`)
var footer_7 = []byte(`<aside class="sidebar">`)
var footer_8 = []byte(`</aside>`)
var footer_9 = []byte(`
<div style="clear: both;"></div> <div style="clear: both;"></div>
</div> </div>
</div> </div>
@ -471,7 +487,7 @@ var profile_14 = []byte(`&type=user" class="profile_menu_item report_item">Repor
<div id="profile_right_lane" class="colstack_right"> <div id="profile_right_lane" class="colstack_right">
`) `)
var profile_15 = []byte(` var profile_15 = []byte(`
<!-- TO-DO: Inline the display: none; CSS --> <!-- TODO: Inline the display: none; CSS -->
<div id="ban_user_head" class="colstack_item colstack_head hash_hide ban_user_hash" style="display: none;"> <div id="ban_user_head" class="colstack_item colstack_head hash_hide ban_user_hash" style="display: none;">
<div class="rowitem"><h1>Ban User</h1></div> <div class="rowitem"><h1>Ban User</h1></div>
</div> </div>
@ -675,23 +691,26 @@ var topics_19 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky
var topics_20 = []byte(` var topics_20 = []byte(`
</span> </span>
</div> </div>
<div class="rowitem topic_right passive datarow" style="`) <div class="rowitem topic_right passive datarow `)
var topics_21 = []byte(`background-image: url(`) var topics_21 = []byte(`topic_sticky`)
var topics_22 = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`) var topics_22 = []byte(`topic_closed`)
var topics_23 = []byte(`"> var topics_23 = []byte(`" style="`)
var topics_24 = []byte(`background-image: url(`)
var topics_25 = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`)
var topics_26 = []byte(`">
<span> <span>
<a href="`) <a href="`)
var topics_24 = []byte(`" class="lastName" style="font-size: 14px;">`) var topics_27 = []byte(`" class="lastName" style="font-size: 14px;">`)
var topics_25 = []byte(`</a><br> var topics_28 = []byte(`</a><br>
<span class="rowsmall lastReplyAt">Last: `) <span class="rowsmall lastReplyAt">Last: `)
var topics_26 = []byte(`</span> var topics_29 = []byte(`</span>
</span> </span>
</div> </div>
`) `)
var topics_27 = []byte(`<div class="rowitem passive">There aren't any topics yet.`) var topics_30 = []byte(`<div class="rowitem passive">There aren't any topics yet.`)
var topics_28 = []byte(` <a href="/topics/create/">Start one?</a>`) var topics_31 = []byte(` <a href="/topics/create/">Start one?</a>`)
var topics_29 = []byte(`</div>`) var topics_32 = []byte(`</div>`)
var topics_30 = []byte(` var topics_33 = []byte(`
</div> </div>
</main> </main>

View File

@ -158,10 +158,26 @@ w.Write(profile_41)
w.Write(profile_42) w.Write(profile_42)
w.Write(profile_43) w.Write(profile_43)
w.Write(footer_0) w.Write(footer_0)
if tmpl_profile_vars.Header.Widgets.RightSidebar != "" { if len(tmpl_profile_vars.Header.Themes) != 0 {
for _, item := range tmpl_profile_vars.Header.Themes {
if !item.HideFromThemes {
w.Write(footer_1) w.Write(footer_1)
w.Write([]byte(string(tmpl_profile_vars.Header.Widgets.RightSidebar))) w.Write([]byte(item.Name))
w.Write(footer_2) w.Write(footer_2)
} if tmpl_profile_vars.Header.ThemeName == item.Name {
w.Write(footer_3) w.Write(footer_3)
} }
w.Write(footer_4)
w.Write([]byte(item.FriendlyName))
w.Write(footer_5)
}
}
}
w.Write(footer_6)
if tmpl_profile_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_7)
w.Write([]byte(string(tmpl_profile_vars.Header.Widgets.RightSidebar)))
w.Write(footer_8)
}
w.Write(footer_9)
}

View File

@ -273,10 +273,26 @@ w.Write(topic_90)
} }
w.Write(topic_91) w.Write(topic_91)
w.Write(footer_0) w.Write(footer_0)
if tmpl_topic_vars.Header.Widgets.RightSidebar != "" { if len(tmpl_topic_vars.Header.Themes) != 0 {
for _, item := range tmpl_topic_vars.Header.Themes {
if !item.HideFromThemes {
w.Write(footer_1) w.Write(footer_1)
w.Write([]byte(string(tmpl_topic_vars.Header.Widgets.RightSidebar))) w.Write([]byte(item.Name))
w.Write(footer_2) w.Write(footer_2)
} if tmpl_topic_vars.Header.ThemeName == item.Name {
w.Write(footer_3) w.Write(footer_3)
} }
w.Write(footer_4)
w.Write([]byte(item.FriendlyName))
w.Write(footer_5)
}
}
}
w.Write(footer_6)
if tmpl_topic_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_7)
w.Write([]byte(string(tmpl_topic_vars.Header.Widgets.RightSidebar)))
w.Write(footer_8)
}
w.Write(footer_9)
}

View File

@ -266,10 +266,26 @@ w.Write(topic_alt_87)
} }
w.Write(topic_alt_88) w.Write(topic_alt_88)
w.Write(footer_0) w.Write(footer_0)
if tmpl_topic_alt_vars.Header.Widgets.RightSidebar != "" { if len(tmpl_topic_alt_vars.Header.Themes) != 0 {
for _, item := range tmpl_topic_alt_vars.Header.Themes {
if !item.HideFromThemes {
w.Write(footer_1) w.Write(footer_1)
w.Write([]byte(string(tmpl_topic_alt_vars.Header.Widgets.RightSidebar))) w.Write([]byte(item.Name))
w.Write(footer_2) w.Write(footer_2)
} if tmpl_topic_alt_vars.Header.ThemeName == item.Name {
w.Write(footer_3) w.Write(footer_3)
} }
w.Write(footer_4)
w.Write([]byte(item.FriendlyName))
w.Write(footer_5)
}
}
}
w.Write(footer_6)
if tmpl_topic_alt_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_7)
w.Write([]byte(string(tmpl_topic_alt_vars.Header.Widgets.RightSidebar)))
w.Write(footer_8)
}
w.Write(footer_9)
}

View File

@ -3,8 +3,8 @@
// 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 "strconv"
// nolint // nolint
func init() { func init() {
@ -115,32 +115,56 @@ if item.Sticky {
w.Write(topics_19) w.Write(topics_19)
} }
w.Write(topics_20) w.Write(topics_20)
if item.LastUser.Avatar != "" { if item.Sticky {
w.Write(topics_21) w.Write(topics_21)
w.Write([]byte(item.LastUser.Avatar)) } else {
if item.IsClosed {
w.Write(topics_22) w.Write(topics_22)
} }
}
w.Write(topics_23) w.Write(topics_23)
w.Write([]byte(item.LastUser.Link)) if item.LastUser.Avatar != "" {
w.Write(topics_24) w.Write(topics_24)
w.Write([]byte(item.LastUser.Name)) w.Write([]byte(item.LastUser.Avatar))
w.Write(topics_25) w.Write(topics_25)
w.Write([]byte(item.LastReplyAt)) }
w.Write(topics_26) w.Write(topics_26)
} w.Write([]byte(item.LastUser.Link))
} else {
w.Write(topics_27) w.Write(topics_27)
if tmpl_topics_vars.CurrentUser.Perms.CreateTopic { w.Write([]byte(item.LastUser.Name))
w.Write(topics_28) w.Write(topics_28)
} w.Write([]byte(item.LastReplyAt))
w.Write(topics_29) w.Write(topics_29)
} }
} else {
w.Write(topics_30) w.Write(topics_30)
w.Write(footer_0) if tmpl_topics_vars.CurrentUser.Perms.CreateTopic {
if tmpl_topics_vars.Header.Widgets.RightSidebar != "" { w.Write(topics_31)
w.Write(footer_1)
w.Write([]byte(string(tmpl_topics_vars.Header.Widgets.RightSidebar)))
w.Write(footer_2)
} }
w.Write(topics_32)
}
w.Write(topics_33)
w.Write(footer_0)
if len(tmpl_topics_vars.Header.Themes) != 0 {
for _, item := range tmpl_topics_vars.Header.Themes {
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_3)
} }
w.Write(footer_4)
w.Write([]byte(item.FriendlyName))
w.Write(footer_5)
}
}
}
w.Write(footer_6)
if tmpl_topics_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_7)
w.Write([]byte(string(tmpl_topics_vars.Header.Widgets.RightSidebar)))
w.Write(footer_8)
}
w.Write(footer_9)
}

View File

@ -13,7 +13,7 @@ import (
"text/template/parse" "text/template/parse"
) )
// TO-DO: Turn this file into a library // TODO: Turn this file into a library
var ctemplates []string var ctemplates []string
var tmplPtrMap = make(map[string]interface{}) var tmplPtrMap = make(map[string]interface{})
var textOverlapList = make(map[string]int) var textOverlapList = make(map[string]int)
@ -263,6 +263,11 @@ func (c *CTemplateSet) compileSwitch(varholder string, holdreflect reflect.Value
for _, key := range outVal.MapKeys() { for _, key := range outVal.MapKeys() {
item = outVal.MapIndex(key) item = outVal.MapIndex(key)
} }
fmt.Println("Range item:", item)
if !item.IsValid() {
panic("item" + "^\n" + "Invalid map. Maybe, it doesn't have any entries for the template engine to analyse?")
}
if node.ElseList != nil { if node.ElseList != nil {
out = "if len(" + out + ") != 0 {\nfor _, item := range " + out + " {\n" + c.compileSwitch("item", item, templateName, node.List) + "}\n} else {\n" + c.compileSwitch("item", item, templateName, node.ElseList) + "}\n" out = "if len(" + out + ") != 0 {\nfor _, item := range " + out + " {\n" + c.compileSwitch("item", item, templateName, node.List) + "}\n} else {\n" + c.compileSwitch("item", item, templateName, node.ElseList) + "}\n"
@ -352,13 +357,23 @@ func (c *CTemplateSet) compileSubswitch(varholder string, holdreflect reflect.Va
} }
if !cur.IsValid() { if !cur.IsValid() {
if dev.DebugMode {
fmt.Println("Debug Data:")
fmt.Println("Holdreflect:", holdreflect)
fmt.Println("Holdreflect.Kind()", holdreflect.Kind())
if !dev.SuperDebug {
fmt.Println("cur.Kind():", cur.Kind().String())
}
fmt.Println("")
}
panic(varholder + varbit + "^\n" + "Invalid value. Maybe, it doesn't exist?") panic(varholder + varbit + "^\n" + "Invalid value. Maybe, it doesn't exist?")
} }
cur = cur.FieldByName(id) cur = cur.FieldByName(id)
if cur.Kind() == reflect.Interface { if cur.Kind() == reflect.Interface {
cur = cur.Elem() cur = cur.Elem()
// TO-DO: Surely, there's a better way of detecting this? // TODO: Surely, there's a better way of detecting this?
/*if cur.Kind() == reflect.String && cur.Type().Name() != "string" { /*if cur.Kind() == reflect.String && cur.Type().Name() != "string" {
varbit = "string(" + varbit + "." + id + ")"*/ varbit = "string(" + varbit + "." + id + ")"*/
//if cur.Kind() == reflect.String && cur.Type().Name() != "string" { //if cur.Kind() == reflect.String && cur.Type().Name() != "string" {
@ -795,7 +810,7 @@ func (c *CTemplateSet) compileIfVarsub(varname string, varholder string, templat
continue continue
} }
// TO-DO: Fix this up so that it works for regular pointers and not just struct pointers. Ditto for the other cur.Kind() == reflect.Ptr we have in this file // TODO: Fix this up so that it works for regular pointers and not just struct pointers. Ditto for the other cur.Kind() == reflect.Ptr we have in this file
if cur.Kind() == reflect.Ptr { if cur.Kind() == reflect.Ptr {
if dev.SuperDebug { if dev.SuperDebug {
fmt.Println("Looping over pointer") fmt.Println("Looping over pointer")
@ -950,7 +965,7 @@ func (c *CTemplateSet) compileSubtemplate(pvarholder string, pholdreflect reflec
} }
} }
// TO-DO: Cascade errors back up the tree to the caller? // TODO: Cascade errors back up the tree to the caller?
res, err := ioutil.ReadFile(c.dir + node.Name) res, err := ioutil.ReadFile(c.dir + node.Name)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -998,7 +1013,7 @@ func (c *CTemplateSet) compileCommand(*parse.CommandNode) (out string) {
panic("Uh oh! Something went wrong!") panic("Uh oh! Something went wrong!")
} }
// TO-DO: Write unit tests for this // TODO: Write unit tests for this
func minify(data string) string { func minify(data string) string {
data = strings.Replace(data, "\t", "", -1) data = strings.Replace(data, "\t", "", -1)
data = strings.Replace(data, "\v", "", -1) data = strings.Replace(data, "\v", "", -1)
@ -1008,30 +1023,30 @@ func minify(data string) string {
return data return data
} }
// TO-DO: Strip comments // TODO: Strip comments
// TO-DO: Handle CSS nested in <style> tags? // TODO: Handle CSS nested in <style> tags?
// TO-DO: Write unit tests for this // TODO: Write unit tests for this
func minifyHTML(data string) string { func minifyHTML(data string) string {
return minify(data) return minify(data)
} }
// TO-DO: Have static files use this // TODO: Have static files use this
// TO-DO: Strip comments // TODO: Strip comments
// TO-DO: Convert the rgb()s to hex codes? // TODO: Convert the rgb()s to hex codes?
// TO-DO: Write unit tests for this // TODO: Write unit tests for this
func minifyCSS(data string) string { func minifyCSS(data string) string {
return minify(data) return minify(data)
} }
// TO-DO: Convert this to three character hex strings whenever possible? // TODO: Convert this to three character hex strings whenever possible?
// TO-DO: Write unit tests for this // TODO: Write unit tests for this
// nolint // nolint
func rgbToHexstr(red int, green int, blue int) string { func rgbToHexstr(red int, green int, blue int) string {
return strconv.FormatInt(int64(red), 16) + strconv.FormatInt(int64(green), 16) + strconv.FormatInt(int64(blue), 16) return strconv.FormatInt(int64(red), 16) + strconv.FormatInt(int64(green), 16) + strconv.FormatInt(int64(blue), 16)
} }
/* /*
// TO-DO: Write unit tests for this // TODO: Write unit tests for this
func hexstrToRgb(hexstr string) (red int, blue int, green int, err error) { func hexstrToRgb(hexstr string) (red int, blue int, green int, err error) {
// Strip the # at the start // Strip the # at the start
if hexstr[0] == '#' { if hexstr[0] == '#' {

View File

@ -8,7 +8,7 @@
<div class="rowitem passive"><a href="/user/edit/critical/">Password</a></div> <div class="rowitem passive"><a href="/user/edit/critical/">Password</a></div>
<div class="rowitem passive"><a href="/user/edit/email/">Email</a></div> <div class="rowitem passive"><a href="/user/edit/email/">Email</a></div>
<div class="rowitem passive"><a href="/user/edit/notifications">Notifications</a></div> <div class="rowitem passive"><a href="/user/edit/notifications">Notifications</a></div>
{{/** TO-DO: Add an alerts page with pagination to go through alerts which either don't fit in the alerts drop-down or which have already been dismissed. Bear in mind though that dismissed alerts older than two weeks might be purged to save space and to speed up the database **/}} {{/** TODO: Add an alerts page with pagination to go through alerts which either don't fit in the alerts drop-down or which have already been dismissed. Bear in mind though that dismissed alerts older than two weeks might be purged to save space and to speed up the database **/}}
<div class="rowitem passive"><a>Coming Soon</a></div> <div class="rowitem passive"><a>Coming Soon</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div> <div class="rowitem passive"><a>Coming Soon</a></div>
</div> </div>

View File

@ -5,7 +5,7 @@
<div class="rowitem"><h1>Emails</h1></div> <div class="rowitem"><h1>Emails</h1></div>
</div> </div>
<div class="colstack_item"> <div class="colstack_item">
<!-- TO-DO: Do we need this inline CSS? --> <!-- TODO: Do we need this inline CSS? -->
{{range .ItemList}} {{range .ItemList}}
<div class="rowitem" style="font-weight: normal;"> <div class="rowitem" style="font-weight: normal;">
<a style="text-transform: none;">{{.Email}}</a> <a style="text-transform: none;">{{.Email}}</a>

View File

@ -1,3 +1,15 @@
<div class="footer">
<div id="poweredBy" style="float: left;margin-top: 4px;">Powered by Gosora - <span style="font-size: 12px;">Made with love by Azareal</span></div>
<form action="/theme/" method="post">
<div id="themeSelector" style="float: right;">
<select id="themeSelectorSelect" name="themeSelector" aria-label="Change the site's appearance">
{{range .Header.Themes}}
{{if not .HideFromThemes}}<option val="{{.Name}}"{{if eq $.Header.ThemeName .Name}} selected{{end}}>{{.FriendlyName}}</option>{{end}}
{{end}}
</select>
</div>
</form>
</div>
</div> </div>
{{if .Header.Widgets.RightSidebar}}<aside class="sidebar">{{.Header.Widgets.RightSidebar}}</aside>{{end}} {{if .Header.Widgets.RightSidebar}}<aside class="sidebar">{{.Header.Widgets.RightSidebar}}</aside>{{end}}
<div style="clear: both;"></div> <div style="clear: both;"></div>

View File

@ -25,7 +25,7 @@
<span> <span>
<a class="rowtopic" href="{{.Link}}">{{.Title}}</a> <a class="rowtopic" href="{{.Link}}">{{.Title}}</a>
<br /><a class="rowsmall" href="{{.Creator.Link}}">Starter: {{.Creator.Name}}</a> <br /><a class="rowsmall" href="{{.Creator.Link}}">Starter: {{.Creator.Name}}</a>
{{/** TO-DO: Avoid the double '|' when both .IsClosed and .Sticky are set to true. We could probably do this with CSS **/}} {{/** TODO: Avoid the double '|' when both .IsClosed and .Sticky are set to true. We could probably do this with CSS **/}}
{{if .IsClosed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>{{end}} {{if .IsClosed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>{{end}}
{{if .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>{{end}} {{if .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>{{end}}
</span> </span>

View File

@ -24,7 +24,7 @@
</span> </span>
</span> </span>
<span style="float: left;"> <span style="float: left;">
{{/** TO-DO: Make sure the forum_active_name class is set and unset when the activity status of this forum is changed **/}} {{/** TODO: Make sure the forum_active_name class is set and unset when the activity status of this forum is changed **/}}
<a data-field="forum_name" data-type="text" class="editable_block forum_name{{if not .Active}} forum_active_name{{end}}">{{.Name}}</a> <a data-field="forum_name" data-type="text" class="editable_block forum_name{{if not .Active}} forum_active_name{{end}}">{{.Name}}</a>
</span> </span>
<br /><span data-field="forum_desc" data-type="text" class="editable_block forum_desc rowsmall">{{.Desc}}</span> <br /><span data-field="forum_desc" data-type="text" class="editable_block forum_desc rowsmall">{{.Desc}}</span>

View File

@ -13,7 +13,7 @@
{{if .Settings}}<a href="/panel/settings/" class="panel_tag">Settings</a>{{end}} {{if .Settings}}<a href="/panel/settings/" class="panel_tag">Settings</a>{{end}}
{{if .Active}}<a href="/panel/plugins/deactivate/{{.UName}}?session={{$.CurrentUser.Session}}" class="panel_tag">Deactivate</a> {{if .Active}}<a href="/panel/plugins/deactivate/{{.UName}}?session={{$.CurrentUser.Session}}" class="panel_tag">Deactivate</a>
{{else if .Installable}} {{else if .Installable}}
{{/** TO-DO: Write a custom template interpreter to fix this nonsense **/}} {{/** TODO: Write a custom template interpreter to fix this nonsense **/}}
{{if .Installed}}<a href="/panel/plugins/activate/{{.UName}}?session={{$.CurrentUser.Session}}" class="panel_tag">Activate</a>{{else}}<a href="/panel/plugins/install/{{.UName}}?session={{$.CurrentUser.Session}}" class="panel_tag">Install</a>{{end}} {{if .Installed}}<a href="/panel/plugins/activate/{{.UName}}?session={{$.CurrentUser.Session}}" class="panel_tag">Activate</a>{{else}}<a href="/panel/plugins/install/{{.UName}}?session={{$.CurrentUser.Session}}" class="panel_tag">Install</a>{{end}}
{{else}}<a href="/panel/plugins/activate/{{.UName}}?session={{$.CurrentUser.Session}}" class="panel_tag">Activate</a>{{end}} {{else}}<a href="/panel/plugins/activate/{{.UName}}?session={{$.CurrentUser.Session}}" class="panel_tag">Activate</a>{{end}}
</span> </span>

View File

@ -26,12 +26,12 @@
<div id="profile_right_lane" class="colstack_right"> <div id="profile_right_lane" class="colstack_right">
{{if .CurrentUser.Perms.BanUsers}} {{if .CurrentUser.Perms.BanUsers}}
<!-- TO-DO: Inline the display: none; CSS --> <!-- TODO: Inline the display: none; CSS -->
<div id="ban_user_head" class="colstack_item colstack_head hash_hide ban_user_hash" style="display: none;"> <div id="ban_user_head" class="colstack_item colstack_head hash_hide ban_user_hash" style="display: none;">
<div class="rowitem"><h1>Ban User</h1></div> <div class="rowitem"><h1>Ban User</h1></div>
</div> </div>
<form id="ban_user_form" class="hash_hide ban_user_hash" action="/users/ban/submit/{{.ProfileOwner.ID}}?session={{.CurrentUser.Session}}" method="post" style="display: none;"> <form id="ban_user_form" class="hash_hide ban_user_hash" action="/users/ban/submit/{{.ProfileOwner.ID}}?session={{.CurrentUser.Session}}" method="post" style="display: none;">
{{/** TO-DO: Put a JS duration calculator here instead of this text? **/}} {{/** TODO: Put a JS duration calculator here instead of this text? **/}}
<div class="colline">If all the fields are left blank, the ban will be permanent.</div> <div class="colline">If all the fields are left blank, the ban will be permanent.</div>
<div class="colstack_item"> <div class="colstack_item">
<div class="formrow real_first_child"> <div class="formrow real_first_child">
@ -100,7 +100,7 @@
</div> </div>
{{/** Quick subpage switcher **/}} {{/** Quick subpage switcher **/}}
{{/** TO-DO: Stop inlining this **/}} {{/** TODO: Stop inlining this **/}}
<script type="text/javascript"> <script type="text/javascript">
function handle_profile_hashbit() { function handle_profile_hashbit() {
var hash_class = "" var hash_class = ""

View File

@ -1,8 +1,8 @@
{{template "header.html" . }} {{template "header.html" . }}
{{/** TO-DO: Move this into a CSS file **/}} {{/** TODO: Move this into a CSS file **/}}
{{template "socialgroups_css.html" . }} {{template "socialgroups_css.html" . }}
{{/** TO-DO: Port the page template functions to the template interpreter **/}} {{/** TODO: Port the page template functions to the template interpreter **/}}
{{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" href="/group/members/{{.SocialGroup.ID}}?page={{subtract .Page 1}}">&lt;</a></div>{{end}} {{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" href="/group/members/{{.SocialGroup.ID}}?page={{subtract .Page 1}}">&lt;</a></div>{{end}}
{{if ne .LastPage .Page}}<link rel="prerender" href="/group/members/{{.SocialGroup.ID}}?page={{add .Page 1}}" /> {{if ne .LastPage .Page}}<link rel="prerender" href="/group/members/{{.SocialGroup.ID}}?page={{add .Page 1}}" />
<div id="nextFloat" class="next_button"><a class="next_link" href="/group/members/{{.SocialGroup.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}} <div id="nextFloat" class="next_button"><a class="next_link" href="/group/members/{{.SocialGroup.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}}

View File

@ -1,8 +1,8 @@
{{template "header.html" . }} {{template "header.html" . }}
{{/** TO-DO: Move this into a CSS file **/}} {{/** TODO: Move this into a CSS file **/}}
{{template "socialgroups_css.html" . }} {{template "socialgroups_css.html" . }}
{{/** TO-DO: Port the page template functions to the template interpreter **/}} {{/** TODO: Port the page template functions to the template interpreter **/}}
{{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" href="/group/{{.SocialGroup.ID}}?page={{subtract .Page 1}}">&lt;</a></div>{{end}} {{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" href="/group/{{.SocialGroup.ID}}?page={{subtract .Page 1}}">&lt;</a></div>{{end}}
{{if ne .LastPage .Page}}<link rel="prerender" href="/group/{{.SocialGroup.ID}}?page={{add .Page 1}}" /> {{if ne .LastPage .Page}}<link rel="prerender" href="/group/{{.SocialGroup.ID}}?page={{add .Page 1}}" />
<div id="nextFloat" class="next_button"><a class="next_link" href="/group/{{.SocialGroup.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}} <div id="nextFloat" class="next_button"><a class="next_link" href="/group/{{.SocialGroup.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}}

View File

@ -13,12 +13,12 @@
<span> <span>
<a class="rowtopic" href="{{.Link}}">{{.Title}}</a> {{if .ForumName}}<a class="rowsmall" href="{{.ForumLink}}">{{.ForumName}}</a>{{end}} <a class="rowtopic" href="{{.Link}}">{{.Title}}</a> {{if .ForumName}}<a class="rowsmall" href="{{.ForumLink}}">{{.ForumName}}</a>{{end}}
<br /><a class="rowsmall" href="{{.Creator.Link}}">Starter: {{.Creator.Name}}</a> <br /><a class="rowsmall" href="{{.Creator.Link}}">Starter: {{.Creator.Name}}</a>
{{/** TO-DO: Avoid the double '|' when both .IsClosed and .Sticky are set to true. We could probably do this with CSS **/}} {{/** TODO: Avoid the double '|' when both .IsClosed and .Sticky are set to true. We could probably do this with CSS **/}}
{{if .IsClosed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>{{end}} {{if .IsClosed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>{{end}}
{{if .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>{{end}} {{if .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>{{end}}
</span> </span>
</div> </div>
<div class="rowitem topic_right passive datarow" style="{{if .LastUser.Avatar}}background-image: url({{.LastUser.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}"> <div class="rowitem topic_right passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}" style="{{if .LastUser.Avatar}}background-image: url({{.LastUser.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}">
<span> <span>
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;">{{.LastUser.Name}}</a><br> <a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;">{{.LastUser.Name}}</a><br>
<span class="rowsmall lastReplyAt">Last: {{.LastReplyAt}}</span> <span class="rowsmall lastReplyAt">Last: {{.LastReplyAt}}</span>

View File

@ -14,11 +14,14 @@ import (
"path/filepath" "path/filepath"
"reflect" "reflect"
"strings" "strings"
"sync"
"sync/atomic"
"text/template" "text/template"
) )
var defaultTheme string
var themes = make(map[string]Theme) var themes = make(map[string]Theme)
var defaultThemeBox atomic.Value
var changeDefaultThemeMutex sync.Mutex
//var overridenTemplates map[string]interface{} = make(map[string]interface{}) //var overridenTemplates map[string]interface{} = make(map[string]interface{})
var overridenTemplates = make(map[string]bool) var overridenTemplates = make(map[string]bool)
@ -65,6 +68,7 @@ type ThemeResource struct {
} }
func LoadThemes() error { func LoadThemes() error {
changeDefaultThemeMutex.Lock()
rows, err := get_themes_stmt.Query() rows, err := get_themes_stmt.Query()
if err != nil { if err != nil {
return err return err
@ -100,7 +104,7 @@ func LoadThemes() error {
if defaultThemeSwitch { if defaultThemeSwitch {
log.Print("Loading the theme '" + theme.Name + "'") log.Print("Loading the theme '" + theme.Name + "'")
theme.Active = true theme.Active = true
defaultTheme = uname defaultThemeBox.Store(uname)
mapThemeTemplates(theme) mapThemeTemplates(theme)
} else { } else {
theme.Active = false theme.Active = false
@ -113,6 +117,7 @@ func LoadThemes() error {
} }
themes[uname] = theme themes[uname] = theme
} }
changeDefaultThemeMutex.Unlock()
return rows.Err() return rows.Err()
} }
@ -158,7 +163,7 @@ func initThemes() error {
} }
func addThemeStaticFiles(theme Theme) error { func addThemeStaticFiles(theme Theme) error {
// TO-DO: Use a function instead of a closure to make this more testable? What about a function call inside the closure to take the theme variable into account? // TODO: Use a function instead of a closure to make this more testable? What about a function call inside the closure to take the theme variable into account?
return filepath.Walk("./themes/"+theme.Name+"/public", func(path string, f os.FileInfo, err error) error { return filepath.Walk("./themes/"+theme.Name+"/public", func(path string, f os.FileInfo, err error) error {
if dev.DebugMode { if dev.DebugMode {
log.Print("Attempting to add static file '" + path + "' for default theme '" + theme.Name + "'") log.Print("Attempting to add static file '" + path + "' for default theme '" + theme.Name + "'")
@ -375,8 +380,8 @@ func resetTemplateOverrides() {
} }
// NEW method of doing theme templates to allow one user to have a different theme to another. Under construction. // NEW method of doing theme templates to allow one user to have a different theme to another. Under construction.
// TO-DO: Generate the type switch instead of writing it by hand // TODO: Generate the type switch instead of writing it by hand
// TO-DO: Cut the number of types in half // TODO: Cut the number of types in half
func RunThemeTemplate(theme string, template string, pi interface{}, w http.ResponseWriter) { func RunThemeTemplate(theme string, template string, pi interface{}, w http.ResponseWriter) {
switch tmplO := GetThemeTemplate(theme, template).(type) { switch tmplO := GetThemeTemplate(theme, template).(type) {
case *func(TopicPage, http.ResponseWriter): case *func(TopicPage, http.ResponseWriter):
@ -415,7 +420,7 @@ func RunThemeTemplate(theme string, template string, pi interface{}, w http.Resp
case func(Page, http.ResponseWriter): case func(Page, http.ResponseWriter):
tmplO(pi.(Page), w) tmplO(pi.(Page), w)
case string: case string:
mapping, ok := themes[defaultTheme].TemplatesMap[template] mapping, ok := themes[defaultThemeBox.Load().(string)].TemplatesMap[template]
if !ok { if !ok {
mapping = template mapping = template
} }
@ -458,7 +463,7 @@ func GetThemeTemplate(theme string, template string) interface{} {
// CreateThemeTemplate creates a theme template on the current default theme // CreateThemeTemplate creates a theme template on the current default theme
func CreateThemeTemplate(theme string, name string) { func CreateThemeTemplate(theme string, name string) {
themes[theme].TmplPtr[name] = func(pi Page, w http.ResponseWriter) { themes[theme].TmplPtr[name] = func(pi Page, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap[name] mapping, ok := themes[defaultThemeBox.Load().(string)].TemplatesMap[name]
if !ok { if !ok {
mapping = name mapping = name
} }
@ -468,3 +473,11 @@ func CreateThemeTemplate(theme string, name string) {
} }
} }
} }
func GetDefaultThemeName() string {
return defaultThemeBox.Load().(string)
}
func SetDefaultThemeName(name string) {
defaultThemeBox.Store(name)
}

View File

@ -6,6 +6,7 @@
"FullImage": "cosmo-conflux.png", "FullImage": "cosmo-conflux.png",
"ForkOf": "cosmo", "ForkOf": "cosmo",
"MobileFriendly": true, "MobileFriendly": true,
"HideFromThemes": true,
"Tag": "🏗️", "Tag": "🏗️",
"URL": "github.com/Azareal/Gosora", "URL": "github.com/Azareal/Gosora",
"Templates": [ "Templates": [

View File

@ -5,6 +5,7 @@
"Creator": "Azareal", "Creator": "Azareal",
"FullImage": "cosmo.png", "FullImage": "cosmo.png",
"MobileFriendly": true, "MobileFriendly": true,
"HideFromThemes": true,
"Tag": "🏗️", "Tag": "🏗️",
"URL": "github.com/Azareal/Gosora", "URL": "github.com/Azareal/Gosora",
"Templates": [ "Templates": [

View File

@ -183,11 +183,11 @@ a {
/* Topic View */ /* Topic View */
/* TO-DO: How should we handle the sticky headers? */ /* TODO: How should we handle the sticky headers? */
.topic_sticky_head { .topic_sticky_head {
} }
/* TO-DO: Rewrite the closed topic header so that it looks more consistent with the rest of the theme */ /* TODO: Rewrite the closed topic header so that it looks more consistent with the rest of the theme */
.topic_closed_head .topic_status_closed { .topic_closed_head .topic_status_closed {
margin-bottom: -10px; margin-bottom: -10px;
font-size: 19px; font-size: 19px;
@ -443,6 +443,24 @@ input, select, textarea {
margin-top: 5px; margin-top: 5px;
} }
.footer {
background-color: rgb(61,61,61);
margin-top: 5px;
padding: 10px;
font-size: 14px;
padding-left: 13px;
padding-right: 13px;
clear: left;
height: 25px;
}
.footer select {
background-color: #444444;
border: 1px solid #555555;
color: #999999;
font-size: 13px;
padding: 4px;
}
/* Forum View */ /* Forum View */
.rowhead { .rowhead {
display: flex; display: flex;
@ -536,7 +554,7 @@ input, select, textarea {
.simple .user_tag { .simple .user_tag {
font-size: 14px; font-size: 14px;
} }
/* TO-DO: Have a has_avatar class for profile comments and topic replies to allow posts without avatars? Won't that look inconsistent next to everything else for just about every theme though? */ /* TODO: Have a has_avatar class for profile comments and topic replies to allow posts without avatars? Won't that look inconsistent next to everything else for just about every theme though? */
#profile_comments .rowitem { #profile_comments .rowitem {
background-repeat: no-repeat, repeat-y; background-repeat: no-repeat, repeat-y;
background-size: 128px; background-size: 128px;

View File

@ -165,7 +165,7 @@ li a {
font-size: 12px; font-size: 12px;
} }
.colblock_left { /*.colblock_left {
border: 1px solid #ccc; border: 1px solid #ccc;
padding: 0px; padding: 0px;
padding-top: 0px; padding-top: 0px;
@ -181,7 +181,7 @@ li a {
overflow: hidden; overflow: hidden;
word-wrap: break-word; word-wrap: break-word;
} }
.colblock_left:empty, .colblock_right:empty { display: none; } .colblock_left:empty, .colblock_right:empty { display: none; }*/
/* The new method of doing columns layouts, colblock is now deprecated :( */ /* The new method of doing columns layouts, colblock is now deprecated :( */
.colstack_left { .colstack_left {
@ -366,6 +366,36 @@ button {
/* Topics */ /* Topics */
.topic_list {
display: grid;
grid-template-columns: calc(100% - 204px) 204px;
}
.topic_list .topic_inner_right {
display: none;
}
.topic_list .rowitem:last-child {
margin-bottom: 10px;
}
.topic_list .lastReplyAt {
white-space: nowrap;
}
@supports not (display: grid) {
.topic_list .rowitem {
float: left;
overflow: hidden;
}
.topic_list .topic_left {
width: calc(100% - 204px);
}
.topic_list .topic_right {
width: 204px;
}
}
.topic_status_sticky {
display: none;
}
.topic_sticky { .topic_sticky {
background-color: rgb(255,255,234); background-color: rgb(255,255,234);
} }
@ -390,7 +420,9 @@ button {
color: #505050; /* 80,80,80 */ color: #505050; /* 80,80,80 */
border-radius: 2px; border-radius: 2px;
} }
.topic_status:empty { display: none; } .topic_status:empty {
display: none;
}
.username, .panel_tag { .username, .panel_tag {
text-transform: none; text-transform: none;
@ -434,7 +466,9 @@ button.username {
color: #202020; color: #202020;
opacity: 0.7; opacity: 0.7;
} }
.post_item > .mod_button > button:hover { opacity: 0.9; } .post_item > .mod_button > button:hover {
opacity: 0.9;
}
.user_tag { .user_tag {
float: right; float: right;
@ -449,8 +483,12 @@ button.username {
background-color: #ffeaff; background-color: #ffeaff;
} }
.mod_button { margin-right: 4px; } .mod_button {
.like_label:before, .like_count_label:before { content: "😀"; } margin-right: 4px;
}
.like_label:before, .like_count_label:before {
content: "😀";
}
.like_count_label { .like_count_label {
color: #505050; color: #505050;
float: right; float: right;
@ -565,6 +603,7 @@ button.username {
background-size: 128px; background-size: 128px;
padding-left: 136px; padding-left: 136px;
} }
/* Profiles */ /* Profiles */
#profile_left_lane { #profile_left_lane {
width: 220px; width: 220px;

View File

@ -18,14 +18,12 @@
.panel_compactrow .panel_tag { .panel_compactrow .panel_tag {
font-size: 14px; font-size: 14px;
} }
.panel_compactrow { .panel_compactrow {
padding-left: 10px; padding-left: 10px;
padding-top: 10px; padding-top: 10px;
padding-bottom: 10px; padding-bottom: 10px;
padding-right: 10px; padding-right: 10px;
} }
.panel_compacttext { .panel_compacttext {
font-size: 14px; font-size: 14px;
} }
@ -39,7 +37,9 @@
font-size: 16px; font-size: 16px;
position: static; position: static;
} }
.panel_upshift:visited { color: black; } .panel_upshift:visited {
color: black;
}
/*.panel_tag_upshift { /*.panel_tag_upshift {
margin-left: 2px; margin-left: 2px;
position: relative; position: relative;
@ -47,31 +47,85 @@
color: #505050; color: #505050;
}*/ }*/
.panel_floater { float: right; } .panel_floater {
.panel_rank_tag_admin:before { content: "👑"; } float: right;
.panel_rank_tag_mod:before { content: "👮"; } }
.panel_rank_tag_banned:before { content: "⛓️"; } .panel_rank_tag_admin:before {
.panel_rank_tag_guest:before { content: "👽"; } content: "👑";
.panel_rank_tag_member:before { content: "👪"; } }
.panel_rank_tag_mod:before {
content: "👮";
}
.panel_rank_tag_banned:before {
content: "⛓️";
}
.panel_rank_tag_guest:before {
content: "👽";
}
.panel_rank_tag_member:before {
content: "👪";
}
.forum_preset_announce:before { content: "📣"; } .forum_preset_announce:before {
.forum_preset_members:before { content: "👪"; } content: "📣";
.forum_preset_staff:before { content: "👮"; } }
.forum_preset_admins:before { content: "👑"; } .forum_preset_members:before {
.forum_preset_archive:before { content: "☠️"; } content: "👪";
.forum_preset_all, .forum_preset_custom, .forum_preset_ { display: none !important; } }
.forum_active_Hide:before { content: "🕵️"; } .forum_preset_staff:before {
.forum_active_Show { display: none !important; } content: "👮";
.forum_active_name { color: #707070; } }
.builtin_forum_divider { border-bottom-style: solid; } .forum_preset_admins:before {
content: "👑";
}
.forum_preset_archive:before {
content: "☠️";
}
.forum_preset_all, .forum_preset_custom, .forum_preset_ {
display: none !important;
}
.forum_active_Hide:before {
content: "🕵️";
}
.forum_active_Show {
display: none !important;
}
.forum_active_name {
color: #707070;
}
.builtin_forum_divider {
border-bottom-style: solid;
}
.perm_preset_no_access:before { content: "No Access"; color: maroon; } .perm_preset_no_access:before {
.perm_preset_read_only:before { content: "Read Only"; color: green; } content: "No Access";
.perm_preset_can_post:before { content: "Can Post"; color: green; } color: maroon;
.perm_preset_can_moderate:before { content: "Can Moderate"; color: darkblue; } }
.perm_preset_custom:before { content: "Custom"; color: black; } .perm_preset_read_only:before {
.perm_preset_default:before { content: "Default"; } content: "Read Only";
color: green;
}
.perm_preset_can_post:before {
content: "Can Post";
color: green;
}
.perm_preset_can_moderate:before {
content: "Can Moderate";
color: darkblue;
}
.perm_preset_custom:before {
content: "Custom";
color: black;
}
.perm_preset_default:before {
content: "Default";
}
#panel_dashboard_right .colstack_head { #panel_dashboard_right .colstack_head {
display: none; display: none;
} }
/* TODO: Figure out how to handle this on the Control Panel */
.footer {
display: none;
}

View File

@ -5,7 +5,7 @@ import "sync"
import "database/sql" import "database/sql"
import "./query_gen/lib" import "./query_gen/lib"
// TO-DO: Add the watchdog goroutine // TODO: Add the watchdog goroutine
var topics TopicStore var topics TopicStore
type TopicStore interface { type TopicStore interface {
@ -165,47 +165,47 @@ func (sts *MemoryTopicStore) GetCapacity() int {
return sts.capacity return sts.capacity
} }
type SqlTopicStore struct { type SQLTopicStore struct {
get *sql.Stmt get *sql.Stmt
} }
func NewSqlTopicStore() *SqlTopicStore { func NewSQLTopicStore() *SQLTopicStore {
stmt, err := qgen.Builder.SimpleSelect("topics", "title, content, createdBy, createdAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data", "tid = ?", "", "") stmt, err := qgen.Builder.SimpleSelect("topics", "title, content, createdBy, createdAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data", "tid = ?", "", "")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
return &SqlTopicStore{stmt} return &SQLTopicStore{stmt}
} }
func (sts *SqlTopicStore) Get(id int) (*Topic, error) { func (sts *SQLTopicStore) Get(id int) (*Topic, error) {
topic := Topic{ID: id} topic := Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &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 (sts *SqlTopicStore) GetUnsafe(id int) (*Topic, error) { func (sts *SQLTopicStore) GetUnsafe(id int) (*Topic, error) {
topic := Topic{ID: id} topic := Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &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 (sts *SqlTopicStore) CascadeGet(id int) (*Topic, error) { func (sts *SQLTopicStore) CascadeGet(id int) (*Topic, error) {
topic := Topic{ID: id} topic := Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &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 (sts *SqlTopicStore) BypassGet(id int) (*Topic, error) { func (sts *SQLTopicStore) BypassGet(id int) (*Topic, error) {
topic := &Topic{ID: id} topic := &Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &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 (sts *SqlTopicStore) Load(id int) error { func (sts *SQLTopicStore) Load(id int) error {
topic := Topic{ID: id} topic := Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &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)
@ -213,29 +213,29 @@ func (sts *SqlTopicStore) Load(id int) error {
} }
// Placeholder methods, the actual queries are done elsewhere // Placeholder methods, the actual queries are done elsewhere
func (sts *SqlTopicStore) Set(item *Topic) error { func (sts *SQLTopicStore) Set(item *Topic) error {
return nil return nil
} }
func (sts *SqlTopicStore) Add(item *Topic) error { func (sts *SQLTopicStore) Add(item *Topic) error {
return nil return nil
} }
func (sts *SqlTopicStore) AddUnsafe(item *Topic) error { func (sts *SQLTopicStore) AddUnsafe(item *Topic) error {
return nil return nil
} }
func (sts *SqlTopicStore) Remove(id int) error { func (sts *SQLTopicStore) Remove(id int) error {
return nil return nil
} }
func (sts *SqlTopicStore) RemoveUnsafe(id int) error { func (sts *SQLTopicStore) RemoveUnsafe(id int) error {
return nil return nil
} }
func (sts *SqlTopicStore) AddLastTopic(item *Topic, fid int) error { func (sts *SQLTopicStore) AddLastTopic(item *Topic, fid int) error {
// Coming Soon... // Coming Soon...
return nil return nil
} }
func (sts *SqlTopicStore) GetCapacity() int { func (sts *SQLTopicStore) GetCapacity() int {
return 0 return 0
} }
func (sts *SqlTopicStore) GetLength() int { func (sts *SQLTopicStore) GetLength() int {
return 0 // Return the total number of topics on the forums? return 0 // Return the total number of topics on the forums?
} }

125
user.go
View File

@ -3,6 +3,7 @@ package main
import ( import (
//"log" //"log"
//"fmt" //"fmt"
"html"
"html/template" "html/template"
"net" "net"
"net/http" "net/http"
@ -15,9 +16,11 @@ import (
var guestUser = User{ID: 0, Link: "#", Group: 6, Perms: GuestPerms} var guestUser = User{ID: 0, Link: "#", Group: 6, Perms: GuestPerms}
var PreRoute func(http.ResponseWriter, *http.Request) (User, bool) = _pre_route // nolint
var PreRoute func(http.ResponseWriter, *http.Request) (User, bool) = preRoute
// TO-DO: Are these even session checks anymore? We might need to rethink these names // TODO: Are these even session checks anymore? We might need to rethink these names
// nolint We need these types so people can tell what they are without scrolling to the bottom of the file
var PanelSessionCheck func(http.ResponseWriter, *http.Request, *User) (*HeaderVars, PanelStats, bool) = _panel_session_check var PanelSessionCheck func(http.ResponseWriter, *http.Request, *User) (*HeaderVars, PanelStats, bool) = _panel_session_check
var SimplePanelSessionCheck func(http.ResponseWriter, *http.Request, *User) (*HeaderLite, bool) = _simple_panel_session_check var SimplePanelSessionCheck func(http.ResponseWriter, *http.Request, *User) (*HeaderLite, bool) = _simple_panel_session_check
var SimpleForumSessionCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *HeaderLite, success bool) = _simple_forum_session_check var SimpleForumSessionCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *HeaderLite, success bool) = _simple_forum_session_check
@ -79,7 +82,7 @@ func (user *User) Unban() error {
return users.Load(user.ID) return users.Load(user.ID)
} }
// TO-DO: Use a transaction to avoid race conditions // TODO: Use a transaction to avoid race conditions
// Make this more stateless? // Make this more stateless?
func (user *User) ScheduleGroupUpdate(gid int, issuedBy int, duration time.Duration) error { func (user *User) ScheduleGroupUpdate(gid int, issuedBy int, duration time.Duration) error {
var temporary bool var temporary bool
@ -99,7 +102,7 @@ func (user *User) ScheduleGroupUpdate(gid int, issuedBy int, duration time.Durat
return users.Load(user.ID) return users.Load(user.ID)
} }
// TO-DO: Use a transaction to avoid race conditions // TODO: Use a transaction to avoid race conditions
func (user *User) RevertGroupUpdate() error { func (user *User) RevertGroupUpdate() error {
_, err := replace_schedule_group_stmt.Exec(user.ID, 0, 0, time.Now(), false) _, err := replace_schedule_group_stmt.Exec(user.ID, 0, 0, time.Now(), false)
if err != nil { if err != nil {
@ -117,18 +120,18 @@ func BcryptCheckPassword(realPassword string, password string, salt string) (err
} }
// Investigate. Do we need the extra salt? // Investigate. Do we need the extra salt?
func BcryptGeneratePassword(password string) (hashed_password string, salt string, err error) { func BcryptGeneratePassword(password string) (hashedPassword string, salt string, err error) {
salt, err = GenerateSafeString(saltLength) salt, err = GenerateSafeString(saltLength)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
password = password + salt password = password + salt
hashed_password, err = BcryptGeneratePasswordNoSalt(password) hashedPassword, err = BcryptGeneratePasswordNoSalt(password)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
return hashed_password, salt, nil return hashedPassword, salt, nil
} }
func BcryptGeneratePasswordNoSalt(password string) (hash string, err error) { func BcryptGeneratePasswordNoSalt(password string) (hash string, err error) {
@ -149,17 +152,18 @@ func SetPassword(uid int, password string) error {
} }
func SendValidationEmail(username string, email string, token string) bool { func SendValidationEmail(username string, email string, token string) bool {
var schema string = "http" var schema = "http"
if site.EnableSsl { if site.EnableSsl {
schema += "s" schema += "s"
} }
// TODO: Move these to the phrase system
subject := "Validate Your Email @ " + site.Name subject := "Validate Your Email @ " + site.Name
msg := "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 + "://" + site.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." msg := "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 + "://" + site.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."
return SendEmail(email, subject, msg) return SendEmail(email, subject, msg)
} }
// TO-DO: Support for left sidebars and sidebars on both sides // TODO: Support for left sidebars and sidebars on both sides
// http.Request is for context.Context middleware. Mostly for plugin_socialgroups right now // http.Request is for context.Context middleware. Mostly for plugin_socialgroups right now
func BuildWidgets(zone string, data interface{}, headerVars *HeaderVars, r *http.Request) { func BuildWidgets(zone string, data interface{}, headerVars *HeaderVars, r *http.Request) {
if vhooks["intercept_build_widgets"] != nil { if vhooks["intercept_build_widgets"] != nil {
@ -168,8 +172,8 @@ func BuildWidgets(zone string, data interface{}, headerVars *HeaderVars, r *http
} }
} }
//log.Print("themes[defaultTheme].Sidebars",themes[defaultTheme].Sidebars) //log.Print("themes[headerVars.ThemeName].Sidebars",themes[headerVars.ThemeName].Sidebars)
if themes[defaultTheme].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 {
@ -258,13 +262,26 @@ func _forum_session_check(w http.ResponseWriter, r *http.Request, user *User, fi
} }
// 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?
func _panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, stats PanelStats, success bool) { func _panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, stats PanelStats, success bool) {
var themeName = defaultThemeBox.Load().(string)
cookie, err := r.Cookie("current_theme")
if err == nil {
cookie := html.EscapeString(cookie.Value)
theme, ok := themes[cookie]
if ok && !theme.HideFromThemes {
themeName = cookie
}
}
headerVars = &HeaderVars{ headerVars = &HeaderVars{
Site: site, Site: site,
Settings: settingBox.Load().(SettingBox), Settings: settingBox.Load().(SettingBox),
ThemeName: defaultTheme, // TO-DO: Is this racey? Themes: themes,
ThemeName: themeName,
} }
// TO-DO: We should probably initialise headerVars.ExtData // TODO: We should probably initialise headerVars.ExtData
if !user.IsSuperMod { if !user.IsSuperMod {
NoPermissions(w, r, *user) NoPermissions(w, r, *user)
@ -272,8 +289,8 @@ func _panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (h
} }
headerVars.Stylesheets = append(headerVars.Stylesheets, headerVars.ThemeName+"/panel.css") headerVars.Stylesheets = append(headerVars.Stylesheets, headerVars.ThemeName+"/panel.css")
if len(themes[defaultTheme].Resources) != 0 { if len(themes[headerVars.ThemeName].Resources) != 0 {
rlist := themes[defaultTheme].Resources rlist := themes[headerVars.ThemeName].Resources
for _, resource := range rlist { for _, resource := range rlist {
if resource.Location == "global" || resource.Location == "panel" { if resource.Location == "global" || resource.Location == "panel" {
halves := strings.Split(resource.Name, ".") halves := strings.Split(resource.Name, ".")
@ -289,18 +306,18 @@ func _panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (h
} }
} }
err := group_count_stmt.QueryRow().Scan(&stats.Groups) err = group_count_stmt.QueryRow().Scan(&stats.Groups)
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
return headerVars, stats, false return headerVars, stats, false
} }
stats.Users = users.GetGlobalCount() stats.Users = users.GetGlobalCount()
stats.Forums = fstore.GetGlobalCount() // TO-DO: Stop it from showing the blanked forums stats.Forums = fstore.GetGlobalCount() // TODO: Stop it from showing the blanked forums
stats.Settings = len(headerVars.Settings) stats.Settings = len(headerVars.Settings)
stats.WordFilters = len(wordFilterBox.Load().(WordFilterBox)) stats.WordFilters = len(wordFilterBox.Load().(WordFilterBox))
stats.Themes = len(themes) stats.Themes = len(themes)
stats.Reports = 0 // TO-DO: Do the report count. Only show open threads? stats.Reports = 0 // TODO: Do the report count. Only show open threads?
pusher, ok := w.(http.Pusher) pusher, ok := w.(http.Pusher)
if ok { if ok {
@ -308,9 +325,9 @@ func _panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (h
pusher.Push("/static/"+headerVars.ThemeName+"/panel.css", nil) pusher.Push("/static/"+headerVars.ThemeName+"/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)
// TO-DO: Push the theme CSS files // TODO: Push the theme CSS files
// TO-DO: Push the theme scripts // TODO: Push the theme scripts
// TO-DO: Push avatars? // TODO: Push avatars?
} }
return headerVars, stats, true return headerVars, stats, true
@ -337,19 +354,32 @@ func _simple_session_check(w http.ResponseWriter, r *http.Request, user *User) (
return headerLite, true return headerLite, true
} }
// TODO: Add the ability for admins to restrict certain themes to certain groups?
func _session_check(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, success bool) { func _session_check(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, success bool) {
var themeName = defaultThemeBox.Load().(string)
cookie, err := r.Cookie("current_theme")
if err == nil {
cookie := html.EscapeString(cookie.Value)
theme, ok := themes[cookie]
if ok && !theme.HideFromThemes {
themeName = cookie
}
}
headerVars = &HeaderVars{ headerVars = &HeaderVars{
Site: site, Site: site,
Settings: settingBox.Load().(SettingBox), Settings: settingBox.Load().(SettingBox),
ThemeName: defaultTheme, // TO-DO: Is this racey? Themes: themes,
ThemeName: themeName,
} }
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[defaultTheme].Resources) != 0 { if len(themes[headerVars.ThemeName].Resources) != 0 {
rlist := themes[defaultTheme].Resources rlist := themes[headerVars.ThemeName].Resources
for _, resource := range rlist { for _, resource := range rlist {
if resource.Location == "global" || resource.Location == "frontend" { if resource.Location == "global" || resource.Location == "frontend" {
halves := strings.Split(resource.Name, ".") halves := strings.Split(resource.Name, ".")
@ -370,15 +400,15 @@ func _session_check(w http.ResponseWriter, r *http.Request, user *User) (headerV
pusher.Push("/static/"+headerVars.ThemeName+"/main.css", nil) pusher.Push("/static/"+headerVars.ThemeName+"/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)
// TO-DO: Push the theme CSS files // TODO: Push the theme CSS files
// TO-DO: Push the theme scripts // TODO: Push the theme scripts
// TO-DO: Push avatars? // TODO: Push avatars?
} }
return headerVars, true return headerVars, true
} }
func _pre_route(w http.ResponseWriter, r *http.Request) (User, bool) { func preRoute(w http.ResponseWriter, r *http.Request) (User, bool) {
user, halt := auth.SessionCheck(w, r) user, halt := auth.SessionCheck(w, r)
if halt { if halt {
return *user, false return *user, false
@ -401,11 +431,14 @@ func _pre_route(w http.ResponseWriter, r *http.Request) (User, bool) {
user.LastIP = host user.LastIP = host
} }
// TO-DO: Set the X-Frame-Options header h := w.Header()
h.Set("X-Frame-Options", "deny")
//h.Set("X-XSS-Protection", "1")
// TODO: Set the content policy header
return *user, true return *user, true
} }
func words_to_score(wcount int, topic bool) (score int) { func wordsToScore(wcount int, topic bool) (score int) {
if topic { if topic {
score = 2 score = 2
} else { } else {
@ -421,11 +454,12 @@ func words_to_score(wcount int, topic bool) (score int) {
return score return score
} }
func increase_post_user_stats(wcount int, uid int, topic bool, user User) error { // TODO: Move this to where the other User methods are
func (user *User) increasePostStats(wcount int, topic bool) error {
var mod int var mod int
baseScore := 1 baseScore := 1
if topic { if topic {
_, err := increment_user_topics_stmt.Exec(1, uid) _, err := increment_user_topics_stmt.Exec(1, user.ID)
if err != nil { if err != nil {
return err return err
} }
@ -434,38 +468,40 @@ func increase_post_user_stats(wcount int, uid int, topic bool, user User) error
settings := settingBox.Load().(SettingBox) settings := settingBox.Load().(SettingBox)
if wcount >= settings["megapost_min_words"].(int) { if wcount >= settings["megapost_min_words"].(int) {
_, err := increment_user_megaposts_stmt.Exec(1, 1, 1, uid) _, err := increment_user_megaposts_stmt.Exec(1, 1, 1, user.ID)
if err != nil { if err != nil {
return err return err
} }
mod = 4 mod = 4
} else if wcount >= settings["bigpost_min_words"].(int) { } else if wcount >= settings["bigpost_min_words"].(int) {
_, err := increment_user_bigposts_stmt.Exec(1, 1, uid) _, err := increment_user_bigposts_stmt.Exec(1, 1, user.ID)
if err != nil { if err != nil {
return err return err
} }
mod = 1 mod = 1
} else { } else {
_, err := increment_user_posts_stmt.Exec(1, uid) _, err := increment_user_posts_stmt.Exec(1, user.ID)
if err != nil { if err != nil {
return err return err
} }
} }
_, err := increment_user_score_stmt.Exec(baseScore+mod, uid) _, err := increment_user_score_stmt.Exec(baseScore+mod, user.ID)
if err != nil { if err != nil {
return err return err
} }
//log.Print(user.Score + base_score + mod) //log.Print(user.Score + base_score + mod)
//log.Print(getLevel(user.Score + base_score + mod)) //log.Print(getLevel(user.Score + base_score + mod))
_, err = update_user_level_stmt.Exec(getLevel(user.Score+baseScore+mod), uid) // TODO: Use a transaction to prevent level desyncs?
_, err = update_user_level_stmt.Exec(getLevel(user.Score+baseScore+mod), user.ID)
return err return err
} }
func decrease_post_user_stats(wcount int, uid int, topic bool, user User) error { // TODO: Move this to where the other User methods are
func (user *User) decreasePostStats(wcount int, topic bool) error {
var mod int var mod int
baseScore := -1 baseScore := -1
if topic { if topic {
_, err := increment_user_topics_stmt.Exec(-1, uid) _, err := increment_user_topics_stmt.Exec(-1, user.ID)
if err != nil { if err != nil {
return err return err
} }
@ -474,28 +510,29 @@ func decrease_post_user_stats(wcount int, uid int, topic bool, user User) error
settings := settingBox.Load().(SettingBox) settings := settingBox.Load().(SettingBox)
if wcount >= settings["megapost_min_words"].(int) { if wcount >= settings["megapost_min_words"].(int) {
_, err := increment_user_megaposts_stmt.Exec(-1, -1, -1, uid) _, err := increment_user_megaposts_stmt.Exec(-1, -1, -1, user.ID)
if err != nil { if err != nil {
return err return err
} }
mod = 4 mod = 4
} else if wcount >= settings["bigpost_min_words"].(int) { } else if wcount >= settings["bigpost_min_words"].(int) {
_, err := increment_user_bigposts_stmt.Exec(-1, -1, uid) _, err := increment_user_bigposts_stmt.Exec(-1, -1, user.ID)
if err != nil { if err != nil {
return err return err
} }
mod = 1 mod = 1
} else { } else {
_, err := increment_user_posts_stmt.Exec(-1, uid) _, err := increment_user_posts_stmt.Exec(-1, user.ID)
if err != nil { if err != nil {
return err return err
} }
} }
_, err := increment_user_score_stmt.Exec(baseScore-mod, uid) _, err := increment_user_score_stmt.Exec(baseScore-mod, user.ID)
if err != nil { if err != nil {
return err return err
} }
_, err = update_user_level_stmt.Exec(getLevel(user.Score-baseScore-mod), uid) // TODO: Use a transaction to prevent level desyncs?
_, err = update_user_level_stmt.Exec(getLevel(user.Score-baseScore-mod), user.ID)
return err return err
} }

View File

@ -12,7 +12,7 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
// TO-DO: Add the watchdog goroutine // TODO: Add the watchdog goroutine
var users UserStore var users UserStore
var errAccountExists = errors.New("this username is already in use") var errAccountExists = errors.New("this username is already in use")
@ -41,30 +41,30 @@ type MemoryUserStore struct {
capacity int capacity int
get *sql.Stmt get *sql.Stmt
register *sql.Stmt register *sql.Stmt
username_exists *sql.Stmt usernameExists *sql.Stmt
user_count *sql.Stmt userCount *sql.Stmt
sync.RWMutex sync.RWMutex
} }
func NewMemoryUserStore(capacity int) *MemoryUserStore { func NewMemoryUserStore(capacity int) *MemoryUserStore {
get_stmt, err := qgen.Builder.SimpleSelect("users", "name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid = ?", "", "") getStmt, err := qgen.Builder.SimpleSelect("users", "name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid = ?", "", "")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
// Add an admin version of register_stmt with more flexibility? // Add an admin version of register_stmt with more flexibility?
// create_account_stmt, err = db.Prepare("INSERT INTO // create_account_stmt, err = db.Prepare("INSERT INTO
register_stmt, err := qgen.Builder.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message", "?,?,?,?,?,0,'',?,''") registerStmt, err := qgen.Builder.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message", "?,?,?,?,?,0,'',?,''")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
username_exists_stmt, err := qgen.Builder.SimpleSelect("users", "name", "name = ?", "", "") usernameExistsStmt, err := qgen.Builder.SimpleSelect("users", "name", "name = ?", "", "")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
user_count_stmt, err := qgen.Builder.SimpleCount("users", "", "") userCountStmt, err := qgen.Builder.SimpleCount("users", "", "")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -72,10 +72,10 @@ func NewMemoryUserStore(capacity int) *MemoryUserStore {
return &MemoryUserStore{ return &MemoryUserStore{
items: make(map[int]*User), items: make(map[int]*User),
capacity: capacity, capacity: capacity,
get: get_stmt, get: getStmt,
register: register_stmt, register: registerStmt,
username_exists: username_exists_stmt, usernameExists: usernameExistsStmt,
user_count: user_count_stmt, userCount: userCountStmt,
} }
} }
@ -135,10 +135,10 @@ func (sus *MemoryUserStore) bulkGet(ids []int) (list []*User) {
return list return list
} }
// TO-DO: 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?
// TO-DO: ID of 0 should always error? // TODO: ID of 0 should always error?
func (sus *MemoryUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, err error) { func (sus *MemoryUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, err error) {
var idCount int = len(ids) var idCount = len(ids)
list = make(map[int]*User) list = make(map[int]*User)
if idCount == 0 { if idCount == 0 {
return list, nil return list, nil
@ -324,7 +324,7 @@ func (sus *MemoryUserStore) RemoveUnsafe(id int) error {
func (sus *MemoryUserStore) CreateUser(username string, password string, email string, group int, active int) (int, error) { func (sus *MemoryUserStore) CreateUser(username string, password string, email string, group int, active int) (int, error) {
// Is this username already taken..? // Is this username already taken..?
err := sus.username_exists.QueryRow(username).Scan(&username) err := sus.usernameExists.QueryRow(username).Scan(&username)
if err != ErrNoRows { if err != ErrNoRows {
return 0, errAccountExists return 0, errAccountExists
} }
@ -363,21 +363,21 @@ func (sus *MemoryUserStore) GetCapacity() int {
// Return the total number of users registered on the forums // Return the total number of users registered on the forums
func (sus *MemoryUserStore) GetGlobalCount() int { func (sus *MemoryUserStore) GetGlobalCount() int {
var ucount int var ucount int
err := sus.user_count.QueryRow().Scan(&ucount) err := sus.userCount.QueryRow().Scan(&ucount)
if err != nil { if err != nil {
LogError(err) LogError(err)
} }
return ucount return ucount
} }
type SqlUserStore struct { type SQLUserStore struct {
get *sql.Stmt get *sql.Stmt
register *sql.Stmt register *sql.Stmt
usernameExists *sql.Stmt usernameExists *sql.Stmt
userCount *sql.Stmt userCount *sql.Stmt
} }
func NewSqlUserStore() *SqlUserStore { func NewSQLUserStore() *SQLUserStore {
getStmt, err := qgen.Builder.SimpleSelect("users", "name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid = ?", "", "") getStmt, err := qgen.Builder.SimpleSelect("users", "name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid = ?", "", "")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -400,7 +400,7 @@ func NewSqlUserStore() *SqlUserStore {
log.Fatal(err) log.Fatal(err)
} }
return &SqlUserStore{ return &SQLUserStore{
get: getStmt, get: getStmt,
register: registerStmt, register: registerStmt,
usernameExists: usernameExistsStmt, usernameExists: usernameExistsStmt,
@ -408,7 +408,7 @@ func NewSqlUserStore() *SqlUserStore {
} }
} }
func (sus *SqlUserStore) Get(id int) (*User, error) { func (sus *SQLUserStore) Get(id int) (*User, error) {
user := User{ID: id, Loggedin: true} user := User{ID: id, Loggedin: true}
err := sus.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 := sus.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)
@ -425,7 +425,7 @@ func (sus *SqlUserStore) Get(id int) (*User, error) {
return &user, err return &user, err
} }
func (sus *SqlUserStore) GetUnsafe(id int) (*User, error) { func (sus *SQLUserStore) GetUnsafe(id int) (*User, error) {
user := User{ID: id, Loggedin: true} user := User{ID: id, Loggedin: true}
err := sus.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 := sus.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)
@ -442,7 +442,7 @@ func (sus *SqlUserStore) GetUnsafe(id int) (*User, error) {
return &user, err return &user, err
} }
func (sus *SqlUserStore) CascadeGet(id int) (*User, error) { func (sus *SQLUserStore) CascadeGet(id int) (*User, error) {
user := User{ID: id, Loggedin: true} user := User{ID: id, Loggedin: true}
err := sus.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 := sus.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)
@ -459,8 +459,8 @@ func (sus *SqlUserStore) CascadeGet(id int) (*User, error) {
return &user, err return &user, err
} }
// TO-DO: 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?
func (sus *SqlUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, err error) { func (sus *SQLUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, err error) {
var qlist string var qlist string
var uidList []interface{} var uidList []interface{}
for _, id := range ids { for _, id := range ids {
@ -506,7 +506,7 @@ func (sus *SqlUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, err e
return list, nil return list, nil
} }
func (sus *SqlUserStore) BypassGet(id int) (*User, error) { func (sus *SQLUserStore) BypassGet(id int) (*User, error) {
user := User{ID: id, Loggedin: true} user := User{ID: id, Loggedin: true}
err := sus.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 := sus.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)
@ -523,13 +523,13 @@ func (sus *SqlUserStore) BypassGet(id int) (*User, error) {
return &user, err return &user, err
} }
func (sus *SqlUserStore) Load(id int) error { func (sus *SQLUserStore) Load(id int) error {
user := &User{ID: id} user := &User{ID: id}
// Simplify this into a quick check to see whether the user exists. Add an Exists method to facilitate this? // Simplify this into a quick check to see whether the user exists. Add an Exists method to facilitate this?
return sus.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) return sus.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)
} }
func (sus *SqlUserStore) CreateUser(username string, password string, email string, group int, active int) (int, error) { func (sus *SQLUserStore) CreateUser(username string, password string, email string, group int, active int) (int, error) {
// Is this username already taken..? // Is this username already taken..?
err := sus.usernameExists.QueryRow(username).Scan(&username) err := sus.usernameExists.QueryRow(username).Scan(&username)
if err != ErrNoRows { if err != ErrNoRows {
@ -556,27 +556,27 @@ func (sus *SqlUserStore) CreateUser(username string, password string, email stri
} }
// Placeholder methods, as we're not don't need to do any cache management with this implementation ofr the UserStore // Placeholder methods, as we're not don't need to do any cache management with this implementation ofr the UserStore
func (sus *SqlUserStore) Set(item *User) error { func (sus *SQLUserStore) Set(item *User) error {
return nil return nil
} }
func (sus *SqlUserStore) Add(item *User) error { func (sus *SQLUserStore) Add(item *User) error {
return nil return nil
} }
func (sus *SqlUserStore) AddUnsafe(item *User) error { func (sus *SQLUserStore) AddUnsafe(item *User) error {
return nil return nil
} }
func (sus *SqlUserStore) Remove(id int) error { func (sus *SQLUserStore) Remove(id int) error {
return nil return nil
} }
func (sus *SqlUserStore) RemoveUnsafe(id int) error { func (sus *SQLUserStore) RemoveUnsafe(id int) error {
return nil return nil
} }
func (sus *SqlUserStore) GetCapacity() int { func (sus *SQLUserStore) GetCapacity() int {
return 0 return 0
} }
// Return the total number of users registered on the forums // Return the total number of users registered on the forums
func (sus *SqlUserStore) GetLength() int { func (sus *SQLUserStore) GetLength() int {
var ucount int var ucount int
err := sus.userCount.QueryRow().Scan(&ucount) err := sus.userCount.QueryRow().Scan(&ucount)
if err != nil { if err != nil {
@ -584,7 +584,7 @@ func (sus *SqlUserStore) GetLength() int {
} }
return ucount return ucount
} }
func (sus *SqlUserStore) GetGlobalCount() int { func (sus *SQLUserStore) GetGlobalCount() int {
var ucount int var ucount int
err := sus.userCount.QueryRow().Scan(&ucount) err := sus.userCount.QueryRow().Scan(&ucount)
if err != nil { if err != nil {

View File

@ -205,16 +205,16 @@ func SendEmail(email string, subject string, msg string) (res bool) {
return return
} }
email_data, err := con.Data() emailData, err := con.Data()
if err != nil { if err != nil {
return return
} }
_, err = fmt.Fprintf(email_data, body) _, err = fmt.Fprintf(emailData, body)
if err != nil { if err != nil {
return return
} }
err = email_data.Close() err = emailData.Close()
if err != nil { if err != nil {
return return
} }
@ -229,7 +229,7 @@ func weakPassword(password string) error {
if len(password) < 8 { if len(password) < 8 {
return errors.New("your password needs to be at-least eight characters long") return errors.New("your password needs to be at-least eight characters long")
} }
var charMap map[rune]int = make(map[rune]int) var charMap = make(map[rune]int)
var numbers /*letters, */, symbols, upper, lower int var numbers /*letters, */, symbols, upper, lower int
for _, char := range password { for _, char := range password {
charItem, ok := charMap[char] charItem, ok := charMap[char]
@ -254,7 +254,7 @@ func weakPassword(password string) error {
} }
} }
// TO-DO: Disable the linter on these and fix up the grammar // TODO: Disable the linter on these and fix up the grammar
if numbers == 0 { if numbers == 0 {
return errors.New("you don't have any numbers in your password") return errors.New("you don't have any numbers in your password")
} }
@ -306,15 +306,15 @@ func wordCount(input string) (count int) {
if input == "" { if input == "" {
return 0 return 0
} }
in_space := false var inSpace bool
for _, value := range input { for _, value := range input {
if unicode.IsSpace(value) { if unicode.IsSpace(value) {
if !in_space { if !inSpace {
in_space = true inSpace = true
} }
} else if in_space { } else if inSpace {
count++ count++
in_space = false inSpace = false
} }
} }
return count + 1 return count + 1
@ -323,14 +323,14 @@ func wordCount(input string) (count int) {
func getLevel(score int) (level int) { func getLevel(score int) (level int) {
var base float64 = 25 var base float64 = 25
var current, prev float64 var current, prev float64
exp_factor := 2.8 expFactor := 2.8
for i := 1; ; i++ { for i := 1; ; i++ {
_, bit := math.Modf(float64(i) / 10) _, bit := math.Modf(float64(i) / 10)
if bit == 0 { if bit == 0 {
exp_factor += 0.1 expFactor += 0.1
} }
current = base + math.Pow(float64(i), exp_factor) + (prev / 3) current = base + math.Pow(float64(i), expFactor) + (prev / 3)
prev = current prev = current
if float64(score) < current { if float64(score) < current {
break break
@ -344,14 +344,14 @@ func getLevelScore(getLevel int) (score int) {
var base float64 = 25 var base float64 = 25
var current, prev float64 var current, prev float64
var level int var level int
exp_factor := 2.8 expFactor := 2.8
for i := 1; ; i++ { for i := 1; ; i++ {
_, bit := math.Modf(float64(i) / 10) _, bit := math.Modf(float64(i) / 10)
if bit == 0 { if bit == 0 {
exp_factor += 0.1 expFactor += 0.1
} }
current = base + math.Pow(float64(i), exp_factor) + (prev / 3) current = base + math.Pow(float64(i), expFactor) + (prev / 3)
prev = current prev = current
level++ level++
if level <= getLevel { if level <= getLevel {
@ -364,16 +364,16 @@ func getLevelScore(getLevel int) (score int) {
func getLevels(maxLevel int) []float64 { func getLevels(maxLevel int) []float64 {
var base float64 = 25 var base float64 = 25
var current, prev float64 // = 0 var current, prev float64 // = 0
exp_factor := 2.8 expFactor := 2.8
var out []float64 var out []float64
out = append(out, 0) out = append(out, 0)
for i := 1; i <= maxLevel; i++ { for i := 1; i <= maxLevel; i++ {
_, bit := math.Modf(float64(i) / 10) _, bit := math.Modf(float64(i) / 10)
if bit == 0 { if bit == 0 {
exp_factor += 0.1 expFactor += 0.1
} }
current = base + math.Pow(float64(i), exp_factor) + (prev / 3) current = base + math.Pow(float64(i), expFactor) + (prev / 3)
prev = current prev = current
out = append(out, current) out = append(out, current)
} }
@ -396,16 +396,10 @@ func buildSlug(slug string, id int) string {
func addModLog(action string, elementID int, elementType string, ipaddress string, actorID int) (err error) { func addModLog(action string, elementID int, elementType string, ipaddress string, actorID int) (err error) {
_, err = add_modlog_entry_stmt.Exec(action, elementID, elementType, ipaddress, actorID) _, err = add_modlog_entry_stmt.Exec(action, elementID, elementType, ipaddress, actorID)
if err != nil {
return err return err
} }
return nil
}
func addAdminLog(action string, elementID string, elementType int, ipaddress string, actorID int) (err error) { func addAdminLog(action string, elementID string, elementType int, ipaddress string, actorID int) (err error) {
_, err = add_adminlog_entry_stmt.Exec(action, elementID, elementType, ipaddress, actorID) _, err = add_adminlog_entry_stmt.Exec(action, elementID, elementType, ipaddress, actorID)
if err != nil {
return err return err
} }
return nil
}

View File

@ -61,7 +61,7 @@ func (hub *WS_Hub) broadcastMessage(msg string) error {
if err != nil { if err != nil {
return err return err
} }
w.Write([]byte(msg)) _, _ = w.Write([]byte(msg))
} }
hub.users.RUnlock() hub.users.RUnlock()
return nil return nil
@ -85,8 +85,8 @@ func (hub *WS_Hub) pushMessage(targetUser int, msg string) error {
return nil return nil
} }
func (hub *WS_Hub) pushAlert(targetUser int, asid int, event string, elementType string, actorID int, targetUser_id int, elementID int) error { func (hub *WS_Hub) pushAlert(targetUser int, asid int, event string, elementType string, actorID int, targetUserID int, elementID int) error {
//log.Print("In push_alert") //log.Print("In pushAlert")
hub.users.RLock() hub.users.RLock()
wsUser, ok := hub.onlineUsers[targetUser] wsUser, ok := hub.onlineUsers[targetUser]
hub.users.RUnlock() hub.users.RUnlock()
@ -95,7 +95,7 @@ func (hub *WS_Hub) pushAlert(targetUser int, asid int, event string, elementType
} }
//log.Print("Building alert") //log.Print("Building alert")
alert, err := buildAlert(asid, event, elementType, actorID, targetUser_id, elementID, *wsUser.User) alert, err := buildAlert(asid, event, elementType, actorID, targetUserID, elementID, *wsUser.User)
if err != nil { if err != nil {
return err return err
} }
@ -107,7 +107,7 @@ func (hub *WS_Hub) pushAlert(targetUser int, asid int, event string, elementType
} }
w.Write([]byte(alert)) w.Write([]byte(alert))
w.Close() _ = w.Close()
return nil return nil
} }
@ -270,45 +270,45 @@ var adminStatsMutex sync.RWMutex
func adminStatsTicker() { func adminStatsTicker() {
time.Sleep(time.Second) time.Sleep(time.Second)
var last_uonline int = -1 var lastUonline = -1
var last_gonline int = -1 var lastGonline = -1
var last_totonline int = -1 var lastTotonline = -1
var last_cpu_perc int = -1 var lastCPUPerc = -1
var last_available_ram int64 = -1 var lastAvailableRAM int64 = -1
var no_stat_updates bool = false var noStatUpdates bool
var no_ram_updates bool = false var noRAMUpdates bool
var onlineColour, onlineGuestsColour, onlineUsersColour, cpustr, cpuColour, ramstr, ramColour string var onlineColour, onlineGuestsColour, onlineUsersColour, cpustr, cpuColour, ramstr, ramColour string
var cpuerr, ramerr error var cpuerr, ramerr error
var memres *mem.VirtualMemoryStat var memres *mem.VirtualMemoryStat
var cpu_perc []float64 var cpuPerc []float64
var totunit, uunit, gunit string var totunit, uunit, gunit string
AdminStatLoop: AdminStatLoop:
for { for {
adminStatsMutex.RLock() adminStatsMutex.RLock()
watch_count := len(adminStatsWatchers) watchCount := len(adminStatsWatchers)
adminStatsMutex.RUnlock() adminStatsMutex.RUnlock()
if watch_count == 0 { if watchCount == 0 {
break AdminStatLoop break AdminStatLoop
} }
cpu_perc, cpuerr = cpu.Percent(time.Duration(time.Second), true) cpuPerc, cpuerr = cpu.Percent(time.Second, true)
memres, ramerr = mem.VirtualMemory() memres, ramerr = mem.VirtualMemory()
uonline := wsHub.userCount() uonline := wsHub.userCount()
gonline := wsHub.guestCount() gonline := wsHub.guestCount()
totonline := uonline + gonline totonline := uonline + gonline
// It's far more likely that the CPU Usage will change than the other stats, so we'll optimise them seperately... // It's far more likely that the CPU Usage will change than the other stats, so we'll optimise them seperately...
no_stat_updates = (uonline == last_uonline && gonline == last_gonline && totonline == last_totonline) noStatUpdates = (uonline == lastUonline && gonline == lastGonline && totonline == lastTotonline)
no_ram_updates = (last_available_ram == int64(memres.Available)) noRAMUpdates = (lastAvailableRAM == int64(memres.Available))
if int(cpu_perc[0]) == last_cpu_perc && no_stat_updates && no_ram_updates { if int(cpuPerc[0]) == lastCPUPerc && noStatUpdates && noRAMUpdates {
time.Sleep(time.Second) time.Sleep(time.Second)
continue continue
} }
if !no_stat_updates { if !noStatUpdates {
if totonline > 10 { if totonline > 10 {
onlineColour = "stat_green" onlineColour = "stat_green"
} else if totonline > 3 { } else if totonline > 3 {
@ -341,7 +341,7 @@ AdminStatLoop:
if cpuerr != nil { if cpuerr != nil {
cpustr = "Unknown" cpustr = "Unknown"
} else { } else {
calcperc := int(cpu_perc[0]) / runtime.NumCPU() calcperc := int(cpuPerc[0]) / runtime.NumCPU()
cpustr = strconv.Itoa(calcperc) cpustr = strconv.Itoa(calcperc)
if calcperc < 30 { if calcperc < 30 {
cpuColour = "stat_green" cpuColour = "stat_green"
@ -352,26 +352,26 @@ AdminStatLoop:
} }
} }
if !no_ram_updates { if !noRAMUpdates {
if ramerr != nil { if ramerr != nil {
ramstr = "Unknown" ramstr = "Unknown"
} else { } else {
total_count, total_unit := convertByteUnit(float64(memres.Total)) totalCount, totalUnit := convertByteUnit(float64(memres.Total))
used_count := convertByteInUnit(float64(memres.Total-memres.Available), total_unit) usedCount := 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
var totstr string var totstr string
if (total_count - float64(int(total_count))) > 0.85 { if (totalCount - float64(int(totalCount))) > 0.85 {
used_count += 1.0 - (total_count - float64(int(total_count))) usedCount += 1.0 - (totalCount - float64(int(totalCount)))
totstr = strconv.Itoa(int(total_count) + 1) totstr = strconv.Itoa(int(totalCount) + 1)
} else { } else {
totstr = fmt.Sprintf("%.1f", total_count) totstr = fmt.Sprintf("%.1f", totalCount)
} }
if used_count > total_count { if usedCount > totalCount {
used_count = total_count usedCount = totalCount
} }
ramstr = fmt.Sprintf("%.1f", used_count) + " / " + totstr + total_unit ramstr = fmt.Sprintf("%.1f", usedCount) + " / " + totstr + totalUnit
ramperc := ((memres.Total - memres.Available) * 100) / memres.Total ramperc := ((memres.Total - memres.Available) * 100) / memres.Total
if ramperc < 50 { if ramperc < 50 {
@ -388,7 +388,7 @@ AdminStatLoop:
watchers := adminStatsWatchers watchers := adminStatsWatchers
adminStatsMutex.RUnlock() adminStatsMutex.RUnlock()
for watcher, _ := range watchers { for watcher := range watchers {
w, err := watcher.conn.NextWriter(websocket.TextMessage) w, err := watcher.conn.NextWriter(websocket.TextMessage)
if err != nil { if err != nil {
//log.Print(err.Error()) //log.Print(err.Error())
@ -398,7 +398,8 @@ AdminStatLoop:
continue continue
} }
if !no_stat_updates { // nolint
if !noStatUpdates {
w.Write([]byte("set #dash-totonline " + strconv.Itoa(totonline) + totunit + " online\r")) w.Write([]byte("set #dash-totonline " + strconv.Itoa(totonline) + totunit + " online\r"))
w.Write([]byte("set #dash-gonline " + strconv.Itoa(gonline) + gunit + " guests online\r")) w.Write([]byte("set #dash-gonline " + strconv.Itoa(gonline) + gunit + " guests online\r"))
w.Write([]byte("set #dash-uonline " + strconv.Itoa(uonline) + uunit + " users online\r")) w.Write([]byte("set #dash-uonline " + strconv.Itoa(uonline) + uunit + " users online\r"))
@ -411,7 +412,7 @@ AdminStatLoop:
w.Write([]byte("set #dash-cpu CPU: " + cpustr + "%\r")) w.Write([]byte("set #dash-cpu CPU: " + cpustr + "%\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 !no_ram_updates { if !noRAMUpdates {
w.Write([]byte("set #dash-ram RAM: " + ramstr + "\r")) w.Write([]byte("set #dash-ram RAM: " + ramstr + "\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"))
} }
@ -419,11 +420,11 @@ AdminStatLoop:
w.Close() w.Close()
} }
last_uonline = uonline lastUonline = uonline
last_gonline = gonline lastGonline = gonline
last_totonline = totonline lastTotonline = totonline
last_cpu_perc = int(cpu_perc[0]) lastCPUPerc = int(cpuPerc[0])
last_available_ram = int64(memres.Available) lastAvailableRAM = int64(memres.Available)
//time.Sleep(time.Second) //time.Sleep(time.Second)
} }