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

View File

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

View File

@ -9,7 +9,7 @@ const CACHE_DYNAMIC int = 1
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.
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.
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
log.Print("Loading the forums.")
fstore = NewStaticForumStore()
fstore = NewMemoryForumStore()
err = fstore.LoadForums()
if err != nil {
return err

View File

@ -7,7 +7,7 @@ import "sync"
import "net/http"
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 errorBuffer []error

View File

@ -15,7 +15,7 @@ var plugins = make(map[string]*Plugin)
var hooks = map[string][]func(interface{}) interface{}{
"forums_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

View File

@ -82,7 +82,7 @@ func addStaticFile(path string, prefix string) error {
func compressBytesGzip(in []byte) []byte {
var buff bytes.Buffer
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()
return buff.Bytes()
}

View File

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

View File

@ -1,12 +1,14 @@
/* Work in progress. Check back later! */
package main
import "log"
import "sync"
import (
"database/sql"
"log"
"sync"
"sync/atomic"
//import "sync/atomic"
import "database/sql"
import "./query_gen/lib"
"./query_gen/lib"
)
var forumUpdateMutex sync.Mutex
var forumCreateMutex sync.Mutex
@ -24,7 +26,7 @@ type ForumStore interface {
Set(forum *Forum) error
//Update(Forum) error
//CascadeUpdate(Forum) error
Delete(id int) error
Delete(id int)
CascadeDelete(id int) error
IncrementTopicCount(id int) error
DecrementTopicCount(id int) error
@ -32,6 +34,8 @@ type ForumStore interface {
Exists(id int) bool
GetAll() ([]*Forum, error)
GetAllIDs() ([]int, error)
GetAllVisible() ([]*Forum, error)
GetAllVisibleIDs() ([]int, error)
//GetChildren(parentID int, parentType string) ([]*Forum,error)
//GetFirstChild(parentID int, parentType string) (*Forum,error)
CreateForum(forumName string, forumDesc string, active bool, preset string) (int, error)
@ -39,17 +43,20 @@ type ForumStore interface {
GetGlobalCount() int
}
type StaticForumStore 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
type MemoryForumStore struct {
//forums map[int]*Forum
forums sync.Map
forumView atomic.Value // []*Forum
//fids []int
forumCapCount int
forumCount int
get *sql.Stmt
getAll *sql.Stmt
forumCount *sql.Stmt
get *sql.Stmt
getAll *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 = ?", "", "")
if err != nil {
log.Fatal(err)
@ -58,30 +65,44 @@ func NewStaticForumStore() *StaticForumStore {
if err != nil {
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 != ''", "")
if err != nil {
log.Fatal(err)
}
return &StaticForumStore{
get: getStmt,
getAll: getAllStmt,
forumCount: forumCountStmt,
return &MemoryForumStore{
get: getStmt,
getAll: getAllStmt,
delete: deleteStmt,
getForumCount: forumCountStmt,
}
}
func (sfs *StaticForumStore) LoadForums() error {
func (mfs *MemoryForumStore) LoadForums() error {
log.Print("Adding the uncategorised forum")
var forums = []*Forum{
&Forum{0, buildForumUrl(nameToSlug("Uncategorised"), 0), "Uncategorised", "", config.UncategorisedForumVisible, "all", 0, "", 0, "", "", 0, "", 0, ""},
forumUpdateMutex.Lock()
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()
if err != nil {
return err
}
defer rows.Close()
var i = 1
var i = 0
for ; rows.Next(); i++ {
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)
@ -89,12 +110,6 @@ func (sfs *StaticForumStore) LoadForums() error {
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 dev.DebugMode {
log.Print("Adding a placeholder forum")
@ -103,132 +118,164 @@ func (sfs *StaticForumStore) LoadForums() error {
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)
forums = append(forums, &forum)
addForum(&forum)
}
err = rows.Err()
if err != nil {
return err
}
sfs.forums = forums
sfs.forumCapCount = i
return nil
mfs.forumCount = i
mfs.forumView.Store(forumView)
return rows.Err()
}
func (sfs *StaticForumStore) DirtyGet(id int) *Forum {
if !((id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name != "") {
// TODO: Hide social groups too
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 sfs.forums[id]
return forum
}
func (sfs *StaticForumStore) Get(id int) (*Forum, error) {
if !((id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name != "") {
func (mfs *MemoryForumStore) Get(id int) (*Forum, error) {
fint, ok := mfs.forums.Load(id)
forum := fint.(*Forum)
if !ok || forum.Name == "" {
return nil, ErrNoRows
}
return sfs.forums[id], nil
return forum, nil
}
func (sfs *StaticForumStore) CascadeGet(id int) (*Forum, error) {
if !((id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name != "") {
return nil, ErrNoRows
func (mfs *MemoryForumStore) CascadeGet(id int) (*Forum, error) {
fint, ok := mfs.forums.Load(id)
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) {
if !((id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name != "") {
return forum, ErrNoRows
func (mfs *MemoryForumStore) CascadeGetCopy(id int) (Forum, error) {
fint, ok := mfs.forums.Load(id)
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}
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
}
func (sfs *StaticForumStore) Load(id int) error {
func (mfs *MemoryForumStore) Load(id int) error {
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 {
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
}
// TO-DO: Set should be able to add new indices not just replace existing ones for consistency with UserStore and TopicStore
func (sfs *StaticForumStore) Set(forum *Forum) error {
forumUpdateMutex.Lock()
if !sfs.Exists(forum.ID) {
forumUpdateMutex.Unlock()
func (mfs *MemoryForumStore) Set(forum *Forum) error {
if !mfs.Exists(forum.ID) {
return ErrNoRows
}
sfs.forums[forum.ID] = forum
forumUpdateMutex.Unlock()
mfs.forums.Store(forum.ID, forum)
mfs.rebuildView()
return nil
}
func (sfs *StaticForumStore) GetAll() ([]*Forum, error) {
return sfs.forums, nil
func (mfs *MemoryForumStore) GetAll() (forumView []*Forum, err error) {
mfs.forums.Range(func(_ interface{}, value interface{}) bool {
forumView = append(forumView, value.(*Forum))
return true
})
return forumView, nil
}
// TO-DO: Implement sub-forums.
/*func (sfs *StaticForumStore) GetChildren(parentID int, parentType string) ([]*Forum,error) {
return nil, nil
func (mfs *MemoryForumStore) GetAllIDs() (ids []int, err error) {
mfs.forums.Range(func(_ interface{}, value interface{}) bool {
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 ;)
// Should we cache this? Well, it's only really used for superadmins right now.
func (sfs *StaticForumStore) GetAllIDs() ([]int, error) {
var max = sfs.forumCapCount
var ids = make([]int, max)
for i := 0; i < max; i++ {
ids[i] = i
func (mfs *MemoryForumStore) GetAllVisible() ([]*Forum, error) {
return mfs.forumView.Load().([]*Forum), nil
}
func (mfs *MemoryForumStore) GetAllVisibleIDs() ([]int, error) {
forumView := mfs.forumView.Load().([]*Forum)
var ids = make([]int, len(forumView))
for i := 0; i < len(forumView); i++ {
ids[i] = forumView[i].ID
}
return ids, nil
}
func (sfs *StaticForumStore) Exists(id int) bool {
return (id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name != ""
// TODO: Implement sub-forums.
/*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()
if !sfs.Exists(id) {
forumUpdateMutex.Unlock()
return nil
}
sfs.forums[id].Name = ""
forumUpdateMutex.Unlock()
return nil
}
func (sfs *StaticForumStore) CascadeDelete(id int) error {
forum, err := sfs.CascadeGet(id)
defer forumUpdateMutex.Unlock()
_, err := mfs.delete.Exec(id)
if err != nil {
return err
}
forumUpdateMutex.Lock()
_, err = delete_forum_stmt.Exec(id)
if err != nil {
return err
}
forum.Name = ""
forumUpdateMutex.Unlock()
mfs.Delete(id)
return nil
}
func (sfs *StaticForumStore) IncrementTopicCount(id int) error {
forum, err := sfs.CascadeGet(id)
func (mfs *MemoryForumStore) IncrementTopicCount(id int) error {
forum, err := mfs.CascadeGet(id)
if err != nil {
return err
}
@ -240,8 +287,8 @@ func (sfs *StaticForumStore) IncrementTopicCount(id int) error {
return nil
}
func (sfs *StaticForumStore) DecrementTopicCount(id int) error {
forum, err := sfs.CascadeGet(id)
func (mfs *MemoryForumStore) DecrementTopicCount(id int) error {
forum, err := mfs.CascadeGet(id)
if err != nil {
return err
}
@ -253,19 +300,19 @@ func (sfs *StaticForumStore) DecrementTopicCount(id int) error {
return nil
}
// TO-DO: 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 {
forum, err := sfs.CascadeGet(fid)
// TODO: Have a pointer to the last topic rather than storing it on the forum itself
func (mfs *MemoryForumStore) UpdateLastTopic(topicName string, tid int, username string, uid int, time string, fid int) error {
forum, err := mfs.CascadeGet(fid)
if err != nil {
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 {
return err
}
forum.LastTopic = topic_name
forum.LastTopic = topicName
forum.LastTopicID = tid
forum.LastReplyer = username
forum.LastReplyerID = uid
@ -274,30 +321,7 @@ func (sfs *StaticForumStore) UpdateLastTopic(topic_name string, tid int, usernam
return nil
}
func (sfs *StaticForumStore) 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
}
func (mfs *MemoryForumStore) CreateForum(forumName string, forumDesc string, active bool, preset string) (int, error) {
forumCreateMutex.Lock()
res, err := create_forum_stmt.Exec(forumName, forumDesc, active, preset)
if err != nil {
@ -308,34 +332,29 @@ func (sfs *StaticForumStore) CreateForum(forumName string, forumDesc string, act
if err != nil {
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, ""})
sfs.forumCapCount++
mfs.forums.Store(fid, &Forum{fid, buildForumURL(nameToSlug(forumName), fid), forumName, forumDesc, active, preset, 0, "", 0, "", "", 0, "", 0, ""})
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)
forumCreateMutex.Unlock()
if active {
mfs.rebuildView()
}
return fid, nil
}
func (sfs *StaticForumStore) fillForumIDGap(biggerID int, smallerID int) {
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?
// TODO: 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
func (sfs *StaticForumStore) GetGlobalCount() (fcount int) {
err := sfs.forumCount.QueryRow().Scan(&fcount)
func (mfs *MemoryForumStore) GetGlobalCount() (fcount int) {
err := mfs.getForumCount.QueryRow().Scan(&fcount)
if err != nil {
LogError(err)
}
return fcount
}
// TO-DO: Work on MapForumStore
// TO-DO: Work on SqlForumStore
// TODO: Work on SqlForumStore

View File

@ -43,6 +43,7 @@ var forum_entry_exists_stmt *sql.Stmt
var group_entry_exists_stmt *sql.Stmt
var get_forum_topics_offset_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_list_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_topics_stmt *sql.Stmt
var edit_profile_reply_stmt *sql.Stmt
var delete_forum_stmt *sql.Stmt
var update_forum_stmt *sql.Stmt
var update_setting_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 set_temp_group_stmt *sql.Stmt
var update_word_filter_stmt *sql.Stmt
var bump_sync_stmt *sql.Stmt
var delete_reply_stmt *sql.Stmt
var delete_topic_stmt *sql.Stmt
var delete_profile_reply_stmt *sql.Stmt
@ -340,6 +341,12 @@ func _gen_mysql() (err error) {
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.")
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 {
@ -658,12 +665,6 @@ func _gen_mysql() (err error) {
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.")
update_forum_stmt, err = db.Prepare("UPDATE `forums` SET `name` = ?,`desc` = ?,`active` = ?,`preset` = ? WHERE `fid` = ?")
if err != nil {
@ -742,6 +743,12 @@ func _gen_mysql() (err error) {
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.")
delete_reply_stmt, err = db.Prepare("DELETE FROM `replies` WHERE `rid` = ?")
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_topics_stmt *sql.Stmt
var edit_profile_reply_stmt *sql.Stmt
var delete_forum_stmt *sql.Stmt
var update_forum_stmt *sql.Stmt
var update_setting_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 set_temp_group_stmt *sql.Stmt
var update_word_filter_stmt *sql.Stmt
var bump_sync_stmt *sql.Stmt
// nolint
func _gen_pgsql() (err error) {
@ -203,12 +203,6 @@ func _gen_pgsql() (err error) {
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.")
update_forum_stmt, err = db.Prepare("UPDATE `forums` SET `name` = ?,`desc` = ?,`active` = ?,`preset` = ? WHERE `fid` = ?")
if err != nil {
@ -286,6 +280,12 @@ func _gen_pgsql() (err error) {
if err != nil {
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
}

View File

@ -65,9 +65,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if dev.SuperDebug {
log.Print("before route_static")
log.Print("prefix:", prefix)
log.Print("req.URL.Path:", req.URL.Path)
log.Print("extra_data:", extra_data)
log.Print("prefix: ", prefix)
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("extra_data: ", extra_data)
log.Print("req.Referer(): ", req.Referer())
}
if prefix == "/static" {
@ -103,6 +104,9 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
case "/forum":
route_forum(w,req,user,extra_data)
return
case "/theme":
route_change_theme(w,req,user)
return
case "/report":
switch(req.URL.Path) {
case "/report/submit/":
@ -230,7 +234,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
case "":
// 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) {
case "robots.txt":
route_robots_txt(w,req)

View File

@ -56,8 +56,8 @@ func gloinit() error {
users = NewMemoryUserStore(config.UserCacheCapacity)
topics = NewMemoryTopicStore(config.TopicCacheCapacity)
} else {
users = NewSqlUserStore()
topics = NewSqlTopicStore()
users = NewSQLUserStore()
topics = NewSQLTopicStore()
}
log.Print("Loading the static files.")
@ -67,7 +67,7 @@ func gloinit() error {
}
auth = NewDefaultAuth()
err = initWordFilters()
err = LoadWordFilters()
if err != nil {
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) {
b.ReportAllocs()
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) {
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) {
if !gloinited {
@ -1166,7 +1166,7 @@ func TestStaticRoute(t *testing.T) {
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) {
if !gloinited {

View File

@ -31,12 +31,12 @@ func _initPgsql() (err error) {
}
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
}
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)
}

48
main.go
View File

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

View File

@ -3,7 +3,7 @@ package main
import "strconv"
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) {
if !gloinited {
err := gloinit()
@ -18,14 +18,14 @@ func TestUserStore(t *testing.T) {
var user *User
var err error
user, err = users.CascadeGet(-1)
_, err = users.CascadeGet(-1)
if err == nil {
t.Error("UID #-1 shouldn't exist")
} else if err != ErrNoRows {
t.Fatal(err)
}
user, err = users.CascadeGet(0)
_, err = users.CascadeGet(0)
if err == nil {
t.Error("UID #0 shouldn't exist")
} 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.")
}
// 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
_, err = users.BulkCascadeGetMap([]int{-1})
if err == nil {
@ -90,7 +90,7 @@ func TestForumStore(t *testing.T) {
var forum *Forum
var err error
forum, err = fstore.CascadeGet(-1)
_, err = fstore.CascadeGet(-1)
if err == nil {
t.Error("FID #-1 shouldn't exist")
} else if err != ErrNoRows {
@ -131,6 +131,8 @@ func TestForumStore(t *testing.T) {
} else if err != nil {
t.Fatal(err)
}
_ = forum
}
func TestSlugs(t *testing.T) {

View File

@ -10,6 +10,8 @@ import (
"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) {
//log.Print("in route_edit_topic")
err := r.ParseForm()
@ -35,7 +37,7 @@ func route_edit_topic(w http.ResponseWriter, r *http.Request, user User) {
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)
if !ok {
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) {
tid, err := strconv.Atoi(r.URL.Path[len("/topic/delete/submit/"):])
if err != nil {
@ -124,7 +127,7 @@ func route_delete_topic(w http.ResponseWriter, r *http.Request, user User) {
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)
if !ok {
return
@ -161,9 +164,15 @@ 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))
http.Redirect(w, r, "/", http.StatusSeeOther)
wcount := wordCount(topic.Content)
err = decrease_post_user_stats(wcount, topic.CreatedBy, true, user)
if err != nil {
topicCreator, err := users.CascadeGet(topic.CreatedBy)
if err == nil {
wcount := wordCount(topic.Content)
err = topicCreator.decreasePostStats(wcount, true)
if err != nil {
InternalError(err, w)
return
}
} else if err != ErrNoRows {
InternalError(err, w)
return
}
@ -192,7 +201,7 @@ func route_stick_topic(w http.ResponseWriter, r *http.Request, user User) {
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)
if !ok {
return
@ -248,7 +257,7 @@ func route_unstick_topic(w http.ResponseWriter, r *http.Request, user User) {
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)
if !ok {
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)
}
// 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) {
err := r.ParseForm()
if err != nil {
@ -327,7 +338,7 @@ func route_reply_edit_submit(w http.ResponseWriter, r *http.Request, user User)
return
}
// TO-DO: Add hooks to make use of headerLite
// TODO: Add hooks to make use of headerLite
_, ok := SimpleForumSessionCheck(w, r, &user, fid)
if !ok {
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) {
err := r.ParseForm()
if err != nil {
@ -377,7 +389,7 @@ func route_reply_delete_submit(w http.ResponseWriter, r *http.Request, user User
return
}
// TO-DO: Add hooks to make use of headerLite
// TODO: Add hooks to make use of headerLite
_, ok := SimpleForumSessionCheck(w, r, &user, fid)
if !ok {
return
@ -399,9 +411,15 @@ func route_reply_delete_submit(w http.ResponseWriter, r *http.Request, user User
w.Write(successJSONBytes)
}
wcount := wordCount(reply.Content)
err = decrease_post_user_stats(wcount, reply.CreatedBy, false, user)
if err != nil {
replyCreator, err := users.CascadeGet(reply.CreatedBy)
if err == nil {
wcount := wordCount(reply.Content)
err = replyCreator.decreasePostStats(wcount, false)
if err != nil {
InternalErrorJSQ(err, w, r, isJs)
return
}
} else if err != ErrNoRows {
InternalErrorJSQ(err, w, r, isJs)
return
}
@ -524,7 +542,7 @@ func route_ips(w http.ResponseWriter, r *http.Request, user User) {
ip := html.EscapeString(r.URL.Path[len("/users/ips/"):])
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)
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
var idSlice []int = make([]int, len(reqUserList))
var idSlice = make([]int, len(reqUserList))
var i int
for userID := range reqUserList {
idSlice[i] = userID
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)
if err != nil {
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) {
headerVars, ok := SessionCheck(w,r,&user)
if !ok {

View File

@ -66,7 +66,7 @@ func _initDatabase() (err error) {
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.")
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 {

View File

@ -24,7 +24,7 @@ CREATE TABLE `forums`(
`active` tinyint DEFAULT 1 not null,
`topicCount` int DEFAULT 0 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,
`lastTopic` varchar(100) DEFAULT '' not null,
`lastTopicID` int DEFAULT 0 not null,
@ -181,16 +181,13 @@ CREATE TABLE `administration_logs`(
`doneAt` datetime not null
);
CREATE TABLE `sync`(
`last_update` int not null,
`node_id` int not null
);
INSERT INTO sync(`last_update`) VALUES (UTC_TIMESTAMP());
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`) VALUES ('bigpost_min_words','250','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 themes(`uname`,`default`) VALUES ('tempra-simple',1);
INSERT INTO emails(`email`,`uid`,`validated`) VALUES ('admin@localhost',1,1);

View File

@ -6,33 +6,33 @@ import "errors"
import "net/http"
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 {
}
func (_ *WS_Hub) guest_count() int {
func (_ *WS_Hub) guestCount() int {
return 0
}
func (_ *WS_Hub) user_count() int {
func (_ *WS_Hub) userCount() int {
return 0
}
func (hub *WS_Hub) broadcast_message(_ string) error {
func (hub *WS_Hub) broadcastMessage(_ string) error {
return nil
}
func (hub *WS_Hub) push_message(_ int, _ string) error {
return wsNouser
func (hub *WS_Hub) pushMessage(_ int, _ string) error {
return errWsNouser
}
func (hub *WS_Hub) push_alert(_ int, _ int, _ string, _ string, _ int, _ int, _ int) error {
return wsNouser
func (hub *WS_Hub) pushAlert(_ int, _ int, _ string, _ string, _ int, _ int, _ int) error {
return errWsNouser
}
func (hub *WS_Hub) push_alerts(_ []int, _ int, _ string, _ string, _ int, _ int, _ int) error {
return wsNouser
func (hub *WS_Hub) pushAlerts(_ []int, _ int, _ string, _ string, _ int, _ int, _ int) error {
return errWsNouser
}
func route_websockets(_ http.ResponseWriter, _ *http.Request, _ User) {

View File

@ -15,11 +15,12 @@ type HeaderVars struct {
Widgets PageWidgets
Site *Site
Settings map[string]interface{}
Themes map[string]Theme // TODO: Use a slice containing every theme instead of the main map for speed
ThemeName string
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 {
Site *Site
Settings SettingBox
@ -36,7 +37,7 @@ type PageWidgets struct {
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 {
items map[string]interface{} // Key: pluginname
sync.RWMutex
@ -531,7 +532,7 @@ func parseMessage(msg string /*, user User*/) string {
}
outbytes = append(outbytes, urlOpen...)
var urlBit = []byte(buildForumUrl("", fid))
var urlBit = []byte(buildForumURL("", fid))
outbytes = append(outbytes, urlBit...)
outbytes = append(outbytes, urlOpen2...)
var fidBit = []byte("#fid-" + strconv.Itoa(fid))
@ -539,7 +540,7 @@ func parseMessage(msg string /*, user User*/) string {
outbytes = append(outbytes, urlClose...)
lastItem = i
} else {
// TO-DO: Forum Shortcode Link
// TODO: Forum Shortcode Link
}
} else if msgbytes[i] == '@' {
//log.Print("IN @")
@ -798,7 +799,7 @@ func coerceIntBytes(data []byte) (res int, length int) {
return conv, i
}
// TO-DO: Write tests for this
// TODO: Write tests for this
func paginate(count int, perPage int, maxPages int) []int {
if count < perPage {
return []int{1}
@ -815,7 +816,7 @@ func paginate(count int, perPage int, maxPages int) []int {
return out
}
// TO-DO: Write tests for this
// TODO: Write tests for this
func pageOffset(count int, page int, perPage int) (int, int, int) {
var offset int
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)
}
// TODO: Revamp this
func route_panel_forums_delete(w http.ResponseWriter, r *http.Request, user User, sfid string) {
headerVars, stats, ok := PanelSessionCheck(w, r, &user)
if !ok {
@ -282,8 +283,8 @@ func route_panel_forums_delete(w http.ResponseWriter, r *http.Request, user User
return
}
confirm_msg := "Are you sure you want to delete the '" + forum.Name + "' forum?"
yousure := AreYouSure{"/panel/forums/delete/submit/" + strconv.Itoa(fid), confirm_msg}
confirmMsg := "Are you sure you want to delete the '" + forum.Name + "' forum?"
yousure := AreYouSure{"/panel/forums/delete/submit/" + strconv.Itoa(fid), confirmMsg}
pi := PanelPage{"Delete Forum", user, headerVars, stats, tList, yousure}
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
}
forum_name := r.PostFormValue("forum_name")
forum_desc := r.PostFormValue("forum_desc")
forum_preset := stripInvalidPreset(r.PostFormValue("forum_preset"))
forum_active := r.PostFormValue("forum_active")
forumName := r.PostFormValue("forum_name")
forumDesc := r.PostFormValue("forum_desc")
forumPreset := stripInvalidPreset(r.PostFormValue("forum_preset"))
forumActive := r.PostFormValue("forum_active")
forum, err := fstore.CascadeGet(fid)
if err == ErrNoRows {
@ -420,40 +421,40 @@ func route_panel_forums_edit_submit(w http.ResponseWriter, r *http.Request, user
return
}
if forum_name == "" {
forum_name = forum.Name
if forumName == "" {
forumName = forum.Name
}
var active bool
if forum_active == "" {
if forumActive == "" {
active = forum.Active
} else if forum_active == "1" || forum_active == "Show" {
} else if forumActive == "1" || forumActive == "Show" {
active = true
} else {
active = false
}
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 {
InternalErrorJSQ(err, w, r, isJs)
return
}
if forum.Name != forum_name {
forum.Name = forum_name
if forum.Name != forumName {
forum.Name = forumName
}
if forum.Desc != forum_desc {
forum.Desc = forum_desc
if forum.Desc != forumDesc {
forum.Desc = forumDesc
}
if forum.Active != active {
forum.Active = active
}
if forum.Preset != forum_preset {
forum.Preset = forum_preset
if forum.Preset != forumPreset {
forum.Preset = forumPreset
}
forumUpdateMutex.Unlock()
permmapToQuery(presetToPermmap(forum_preset), fid)
permmapToQuery(presetToPermmap(forumPreset), fid)
if !isJs {
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
}
perm_preset := stripInvalidGroupForumPreset(r.PostFormValue("perm_preset"))
fperms, changed := groupForumPresetToForumPerms(perm_preset)
permPreset := stripInvalidGroupForumPreset(r.PostFormValue("perm_preset"))
fperms, changed := groupForumPresetToForumPerms(permPreset)
forum, err := fstore.CascadeGet(fid)
if err == ErrNoRows {
@ -519,7 +520,7 @@ func route_panel_forums_edit_perms_submit(w http.ResponseWriter, r *http.Request
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 {
InternalErrorJSQ(err, w, r, isJs)
return
@ -552,7 +553,7 @@ func route_panel_settings(w http.ResponseWriter, r *http.Request, user User) {
}
//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()
if err != nil {
InternalError(err, w)
@ -560,6 +561,7 @@ func route_panel_settings(w http.ResponseWriter, r *http.Request, user User) {
}
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 sname, scontent, stype string
for rows.Next() {
@ -722,7 +724,7 @@ func route_panel_word_filters(w http.ResponseWriter, r *http.Request, user User)
return
}
var filterList WordFilterBox = wordFilterBox.Load().(WordFilterBox)
var filterList = wordFilterBox.Load().(WordFilterBox)
pi := PanelPage{"Word Filter Manager", user, headerVars, stats, tList, filterList}
if preRenderHooks["pre_render_panel_word_filters"] != nil {
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)
return
}
lastId, err := res.LastInsertId()
lastID, err := res.LastInsertId()
if err != nil {
InternalErrorJSQ(err, w, r, isJs)
return
}
addWordFilter(int(lastId), find, replacement)
http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther) // TO-DO: Return json for JS?
addWordFilter(int(lastID), find, replacement)
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) {
@ -811,7 +813,7 @@ func route_panel_word_filters_edit_submit(w http.ResponseWriter, r *http.Request
PreError("Bad Form", w, r)
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")
if !user.Perms.EditSettings {
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)
return
}
var has_plugin bool = (err == nil)
var hasPlugin = (err == nil)
if plugins[uname].Activate != nil {
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("active",active)
if has_plugin {
if hasPlugin {
if active {
LocalError("The plugin is already active", w, r, user)
return
@ -1071,7 +1073,7 @@ func route_panel_plugins_install(w http.ResponseWriter, r *http.Request, user Us
InternalError(err, w)
return
}
var has_plugin bool = (err == nil)
var hasPlugin = (err == nil)
if plugins[uname].Install != nil {
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)
if err != nil {
InternalError(err, w)
@ -1139,7 +1141,7 @@ func route_panel_users(w http.ResponseWriter, r *http.Request, user User) {
}
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() {
puser := User{ID: 0}
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
}
// TO-DO: Load the phrases in bulk for efficiency?
// TODO: Load the phrases in bulk for efficiency?
var localPerms []NameLangToggle
localPerms = append(localPerms, NameLangToggle{"ViewTopic", GetLocalPermPhrase("ViewTopic"), group.Perms.ViewTopic})
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)
if err != nil {
InternalError(err, w)
@ -1896,9 +1900,10 @@ func route_panel_themes_set_default(w http.ResponseWriter, r *http.Request, user
dTheme.Active = false
themes[defaultTheme] = dTheme
defaultTheme = uname // TO-DO: Make this less racey
defaultThemeBox.Store(uname)
resetTemplateOverrides()
mapThemeTemplates(theme)
changeDefaultThemeMutex.Unlock()
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 {
switch preset {
case "all":
@ -530,6 +530,7 @@ func presetToLang(preset string) string {
}
}
// TODO: Is this racey?
func rebuildGroupPermissions(gid int) error {
var permstr []byte
log.Print("Reloading a group")
@ -538,15 +539,15 @@ func rebuildGroupPermissions(gid int) error {
return err
}
tmp_perms := Perms{
tmpPerms := Perms{
//ExtData: make(map[string]bool),
}
err = json.Unmarshal(permstr, &tmp_perms)
err = json.Unmarshal(permstr, &tmpPerms)
if err != nil {
return err
}
groups[gid].Perms = tmp_perms
groups[gid].Perms = tmpPerms
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) {
perms.ViewTopic = status
perms.LikeItem = status

View File

@ -10,7 +10,7 @@ import "database/sql"
import _ "github.com/lib/pq"
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 get_activity_feed_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) {
// 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
if(dbpassword != ""){
_dbpassword = " password='" + _escape_bit(db_config.Password) + "'"
@ -68,6 +68,6 @@ func _init_database() (err error) {
}
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)
}

View File

@ -11,7 +11,8 @@ import (
"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 currentLanguage = "english"
var currentLangPack atomic.Value
@ -33,7 +34,8 @@ type LanguagePack struct {
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{
"english": &LanguagePack{
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) {
changeLangpackMutex.Lock()
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 = bbcodeQuotes.ReplaceAllString(msg, "<span class='postQuote'>$1</span>")
return bbcodeCode.ReplaceAllString(msg, "<span class='codequotes'>$1</span>")
} else {
return string(msgbytes)
}
return string(msgbytes)
}
// Does every type of BBCode
@ -368,7 +367,7 @@ func bbcodeFullParse(msg string) string {
goto MainLoop
}
// TO-DO: Add support for negative numbers?
// TODO: Add support for negative numbers?
if number < 0 {
outbytes = append(outbytes, bbcodeNoNegative...)
goto MainLoop

View File

@ -4,7 +4,7 @@ package main
import "regexp"
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 markdownBoldTagOpen, markdownBoldTagClose []byte
@ -88,7 +88,7 @@ func _markdownParse(msg string, n int) string {
switch msg[index] {
case '_':
var startIndex int = index
var startIndex = index
if (index + 1) >= len(msg) {
break
}
@ -111,7 +111,7 @@ func _markdownParse(msg string, n int) string {
lastElement = index
index--
case '~':
var startIndex int = index
var startIndex = index
if (index + 1) >= len(msg) {
break
}
@ -142,8 +142,8 @@ func _markdownParse(msg string, n int) string {
//log.Print("start string(msg[index])",string(msg[index]))
//log.Print("start []byte(msg[:index])",[]byte(msg[:index]))
var startIndex int = index
var italic bool = true
var startIndex = index
var italic = true
var bold bool
if (index + 2) < len(msg) {
//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
func markdownSkipList(data string, index int) int {
var lastNewline int
var datalen int = len(data)
var datalen = len(data)
for ; index < datalen; index++ {
SkipListInnerLoop:

View File

@ -25,7 +25,7 @@ var socialgroupsAttachForumStmt *sql.Stmt
var socialgroupsUnattachForumStmt *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
type SocialGroup struct {
@ -87,12 +87,12 @@ type SocialGroupMember struct {
RankString string /* Member, Mod, Admin, Owner */
PostCount int
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
}
// 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() {
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("simple_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")
router.HandleFunc("/groups/", socialgroupsGroupList)
router.HandleFunc("/group/", socialgroupsViewGroup)
@ -175,7 +175,7 @@ func deactivateSocialgroups() {
_ = 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 {
sgTableStmt, err := qgen.Builder.CreateTable("socialgroups", "utf8mb4", "utf8mb4_general_ci",
[]qgen.DB_Table_Column{
@ -229,9 +229,9 @@ func uninstallSocialgroups() error {
return nil
}
// TO-DO: Do this properly via the widget system
// TODO: Do this properly via the widget system
func socialgroupsCommonAreaWidgets(headerVars *HeaderVars) {
// TO-DO: Hot Groups? Featured Groups? Official Groups?
// TODO: Hot Groups? Featured Groups? Official Groups?
var b bytes.Buffer
var menu = WidgetMenu{"Social Groups", []WidgetMenuItem{
WidgetMenuItem{"Create Group", "/group/create/", false},
@ -243,15 +243,15 @@ func socialgroupsCommonAreaWidgets(headerVars *HeaderVars) {
return
}
if themes[defaultTheme].Sidebars == "left" {
if themes[headerVars.ThemeName].Sidebars == "left" {
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()))
}
}
// TO-DO: Do this properly via the widget system
// TO-DO: Make a better more customisable group widget system
// TODO: Do this properly via the widget system
// TODO: Make a better more customisable group widget system
func socialgroupsGroupWidgets(headerVars *HeaderVars, sgItem *SocialGroup) (success bool) {
return false // Disabled until the next commit
@ -267,9 +267,9 @@ func socialgroupsGroupWidgets(headerVars *HeaderVars, sgItem *SocialGroup) (succ
return false
}
if themes[defaultTheme].Sidebars == "left" {
if themes[headerVars.ThemeName].Sidebars == "left" {
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()))
} else {
return false
@ -356,7 +356,7 @@ func socialgroupsCreateGroup(w http.ResponseWriter, r *http.Request, user User)
if !ok {
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"] {
NoPermissions(w, r, user)
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) {
// 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"] {
NoPermissions(w, r, user)
return
@ -561,7 +561,7 @@ func socialgroupsTrowAssign(args ...interface{}) interface{} {
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{} {
var fid = args[2].(int)
if fstore.DirtyGet(fid).ParentType == "socialgroup" {
@ -571,9 +571,9 @@ func socialgroupsTopicCreatePreLoop(args ...interface{}) interface{} {
return nil
}
// TO-DO: Add privacy options
// TO-DO: Add support for multiple boards and add per-board simplified permissions
// TO-DO: Take isJs into account for routes which expect JSON responses
// TODO: Add privacy options
// TODO: Add support for multiple boards and add per-board simplified permissions
// TODO: Take isJs into account for routes which expect JSON responses
func socialgroupsForumCheck(args ...interface{}) (skip interface{}) {
var r = args[1].(*http.Request)
var fid = args[3].(*int)
@ -604,10 +604,10 @@ func socialgroupsForumCheck(args ...interface{}) (skip interface{}) {
var posts int
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
// 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)
user.Perms.ViewTopic = true
@ -620,7 +620,7 @@ func socialgroupsForumCheck(args ...interface{}) (skip interface{}) {
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 {
return true
}
@ -641,7 +641,7 @@ func socialgroupsForumCheck(args ...interface{}) (skip interface{}) {
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{} {
var zone = args[0].(string)

View File

@ -3,7 +3,7 @@ package main
import "strconv"
import "testing"
// TO-DO: Replace the soft tabs with hard ones
// TODO: Replace the soft tabs with hard ones
// go test -v
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)
{
var alertListNode = menu_alerts.getElementsByClassName("alertList")[0];
@ -119,7 +119,7 @@ $(document).ready(function(){
conn.onopen = function() {
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();
}
conn.onclose = function() {
@ -148,15 +148,15 @@ $(document).ready(function(){
for (var i = 0; i < alertList.length; i++) alist += alertList[i];
//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 alertListNode = general_alerts.getElementsByClassName("alertList")[0];
var alertCounterNode = general_alerts.getElementsByClassName("alert_counter")[0];
alertListNode.innerHTML = alist;
alertCounterNode.textContent = alertCount;
// TO-DO: 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: Add some sort of notification queue to avoid flooding the end-user with notices?
// TODO: Use the site name instead of "Something Happened"
if(Notification.permission === "granted") {
var n = new Notification("Something Happened",{
body: msg,
@ -354,7 +354,7 @@ $(document).ready(function(){
});
// 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(){
var ip = this.textContent;
if(ip.length > 10){
@ -396,6 +396,32 @@ $(document).ready(function(){
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) {
if(event.which == 37) this.querySelectorAll("#prevFloat 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
// 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 {
adapter DB_Adapter
instructions []DB_Install_Instruction

View File

@ -54,7 +54,7 @@ func (adapter *Mysql_Adapter) CreateTable(name string, table string, charset str
}
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" {
end = " DEFAULT "
if adapter.stringyType(column.Type) && column.Default != "''" {
@ -814,7 +814,7 @@ func (adapter *Mysql_Adapter) Write() error {
var stmts, body string
for _, name := range adapter.BufferOrder {
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" {
stmts += "var " + name + "_stmt *sql.Stmt\n"
body += `

View File

@ -29,7 +29,7 @@ func (adapter *Pgsql_Adapter) GetStmts() map[string]DB_Stmt {
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
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 == "" {
@ -93,7 +93,7 @@ func (adapter *Pgsql_Adapter) CreateTable(name string, table string, charset str
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) {
if name == "" {
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
}
// TO-DO: Implement this
// TODO: Implement this
func (adapter *Pgsql_Adapter) SimpleReplace(name string, table string, columns string, fields string) (string, error) {
if name == "" {
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
}
// 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) {
if name == "" {
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 {
switch token.Type {
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()" {
token.Contents = "LOCALTIMESTAMP()"
}
@ -170,7 +170,7 @@ func (adapter *Pgsql_Adapter) SimpleUpdate(name string, table string, set string
for _, token := range loc.Expr {
switch token.Type {
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()" {
token.Contents = "LOCALTIMESTAMP()"
}
@ -194,7 +194,7 @@ func (adapter *Pgsql_Adapter) SimpleUpdate(name string, table string, set string
return querystr, nil
}
// TO-DO: Implement this
// TODO: Implement this
func (adapter *Pgsql_Adapter) SimpleDelete(name string, table string, where string) (string, error) {
if name == "" {
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
}
// 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
func (adapter *Pgsql_Adapter) Purge(name string, table string) (string, error) {
if name == "" {
@ -220,7 +220,7 @@ func (adapter *Pgsql_Adapter) Purge(name string, table string) (string, error) {
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) {
if name == "" {
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
}
// 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) {
if name == "" {
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
}
// 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) {
if name == "" {
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
}
// TO-DO: Implement this
// TODO: Implement this
func (adapter *Pgsql_Adapter) SimpleInsertSelect(name string, ins DB_Insert, sel DB_Select) (string, error) {
return "", nil
}
// TO-DO: Implement this
// TODO: Implement this
func (adapter *Pgsql_Adapter) SimpleInsertLeftJoin(name string, ins DB_Insert, sel DB_Join) (string, error) {
return "", nil
}
// TO-DO: Implement this
// TODO: Implement this
func (adapter *Pgsql_Adapter) SimpleInsertInnerJoin(name string, ins DB_Insert, sel DB_Join) (string, error) {
return "", nil
}
// TO-DO: Implement this
// TODO: Implement this
func (adapter *Pgsql_Adapter) SimpleCount(name string, table string, where string, limit string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
@ -304,7 +304,7 @@ func (adapter *Pgsql_Adapter) Write() error {
var stmts, body string
for _, name := range adapter.BufferOrder {
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" {
stmts += "var " + name + "_stmt *sql.Stmt\n"
body += `

View File

@ -121,7 +121,7 @@ type DB_Adapter interface {
SimpleCount(string,string,string,string) (string, 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) {

View File

@ -15,465 +15,484 @@ func main() {
}
}
// nolint
func write_statements(adapter qgen.DB_Adapter) error {
err := create_tables(adapter)
if err != nil {
return err
}
err = seed_tables(adapter)
if err != nil {
return err
}
err = write_selects(adapter)
if err != nil {
return err
}
err = write_left_joins(adapter)
if err != nil {
return err
}
err = write_inner_joins(adapter)
if err != nil {
return err
}
err = write_inserts(adapter)
if err != nil {
return err
}
err = write_replaces(adapter)
if err != nil {
return err
}
err = write_updates(adapter)
if err != nil {
return err
}
err = write_deletes(adapter)
if err != nil {
return err
}
err = write_simple_counts(adapter)
if err != nil {
return err
}
err = write_insert_selects(adapter)
if err != nil {
return err
}
err = write_insert_left_joins(adapter)
if err != nil {
return err
}
err = write_insert_inner_joins(adapter)
if err != nil {
return err
}
return nil
}
// nolint
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{"uid","int",0,false,true,""},
qgen.DB_Table_Column{"name","varchar",100,false,false,""},
qgen.DB_Table_Column{"password","varchar",100,false,false,""},
qgen.DB_Table_Column{"salt","varchar",80,false,false,"''"},
qgen.DB_Table_Column{"group","int",0,false,false,""},
qgen.DB_Table_Column{"active","boolean",0,false,false,"0"},
qgen.DB_Table_Column{"is_super_admin","boolean",0,false,false,"0"},
qgen.DB_Table_Column{"createdAt","createdAt",0,false,false,""},
qgen.DB_Table_Column{"lastActiveAt","datetime",0,false,false,""},
qgen.DB_Table_Column{"session","varchar",200,false,false,"''"},
qgen.DB_Table_Column{"last_ip","varchar",200,false,false,"0.0.0.0.0"},
qgen.DB_Table_Column{"email","varchar",200,false,false,"''"},
qgen.DB_Table_Column{"avatar","varchar",100,false,false,"''"},
qgen.DB_Table_Column{"message","text",0,false,false,"''"},
qgen.DB_Table_Column{"url_prefix","varchar",20,false,false,"''"},
qgen.DB_Table_Column{"url_name","varchar",100,false,false,"''"},
qgen.DB_Table_Column{"level","smallint",0,false,false,"0"},
qgen.DB_Table_Column{"score","int",0,false,false,"0"},
qgen.DB_Table_Column{"posts","int",0,false,false,"0"},
qgen.DB_Table_Column{"bigposts","int",0,false,false,"0"},
qgen.DB_Table_Column{"megaposts","int",0,false,false,"0"},
qgen.DB_Table_Column{"topics","int",0,false,false,"0"},
qgen.DB_Table_Column{"uid", "int", 0, false, true, ""},
qgen.DB_Table_Column{"name", "varchar", 100, false, false, ""},
qgen.DB_Table_Column{"password", "varchar", 100, false, false, ""},
qgen.DB_Table_Column{"salt", "varchar", 80, false, false, "''"},
qgen.DB_Table_Column{"group", "int", 0, false, false, ""},
qgen.DB_Table_Column{"active", "boolean", 0, false, false, "0"},
qgen.DB_Table_Column{"is_super_admin", "boolean", 0, false, false, "0"},
qgen.DB_Table_Column{"createdAt", "createdAt", 0, false, false, ""},
qgen.DB_Table_Column{"lastActiveAt", "datetime", 0, false, false, ""},
qgen.DB_Table_Column{"session", "varchar", 200, false, false, "''"},
qgen.DB_Table_Column{"last_ip", "varchar", 200, false, false, "0.0.0.0.0"},
qgen.DB_Table_Column{"email", "varchar", 200, false, false, "''"},
qgen.DB_Table_Column{"avatar", "varchar", 100, false, false, "''"},
qgen.DB_Table_Column{"message", "text", 0, false, false, "''"},
qgen.DB_Table_Column{"url_prefix", "varchar", 20, false, false, "''"},
qgen.DB_Table_Column{"url_name", "varchar", 100, false, false, "''"},
qgen.DB_Table_Column{"level", "smallint", 0, false, false, "0"},
qgen.DB_Table_Column{"score", "int", 0, false, false, "0"},
qgen.DB_Table_Column{"posts", "int", 0, false, false, "0"},
qgen.DB_Table_Column{"bigposts", "int", 0, false, false, "0"},
qgen.DB_Table_Column{"megaposts", "int", 0, false, false, "0"},
qgen.DB_Table_Column{"topics", "int", 0, false, false, "0"},
//qgen.DB_Table_Column{"penalty_count","int",0,false,false,"0"},
qgen.DB_Table_Column{"temp_group","int",0,false,false,"0"}, // For temporary groups, set this to zero when a temporary group isn't in effect
qgen.DB_Table_Column{"temp_group", "int", 0, false, false, "0"}, // For temporary groups, set this to zero when a temporary group isn't in effect
},
[]qgen.DB_Table_Key{
qgen.DB_Table_Key{"uid","primary"},
qgen.DB_Table_Key{"name","unique"},
qgen.DB_Table_Key{"uid", "primary"},
qgen.DB_Table_Key{"name", "unique"},
},
)
// 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?
// TO-DO: 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
// TO-DO: 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: Perm overrides
// TODO: 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 penalty type where a user is stopped from creating plugin_socialgroups social groups
// 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.DB_Table_Column{
qgen.DB_Table_Column{"uid","int",0,false,false,""},
qgen.DB_Table_Column{"element_id","int",0,false,false,""},
qgen.DB_Table_Column{"element_type","varchar",50,false,false,""}, //forum, profile?, and social_group. Leave blank for global.
qgen.DB_Table_Column{"overrides","text",0,false,false,"{}"},
qgen.DB_Table_Column{"mod_queue","boolean",0,false,false,"0"},
qgen.DB_Table_Column{"shadow_ban","boolean",0,false,false,"0"},
qgen.DB_Table_Column{"no_avatar","boolean",0,false,false,"0"}, // Coming Soon. Should this be a perm override instead?
// Do we *really* need rate-limit penalty types? Are we going to be allowing bots or something?
//qgen.DB_Table_Column{"posts_per_hour","int",0,false,false,"0"},
//qgen.DB_Table_Column{"topics_per_hour","int",0,false,false,"0"},
//qgen.DB_Table_Column{"posts_count","int",0,false,false,"0"},
//qgen.DB_Table_Column{"topic_count","int",0,false,false,"0"},
//qgen.DB_Table_Column{"last_hour","int",0,false,false,"0"}, // UNIX Time, as we don't need to do anything too fancy here. When an hour has elapsed since that time, reset the hourly penalty counters.
qgen.DB_Table_Column{"issued_by","int",0,false,false,""},
qgen.DB_Table_Column{"issued_at","createdAt",0,false,false,""},
qgen.DB_Table_Column{"expires_at","datetime",0,false,false,""},
},
[]qgen.DB_Table_Key{},
)*/
qgen.Install.CreateTable("users_groups_scheduler","","",
qgen.Install.CreateTable("users_groups_scheduler", "", "",
[]qgen.DB_Table_Column{
qgen.DB_Table_Column{"uid","int",0,false,false,""},
qgen.DB_Table_Column{"set_group","int",0,false,false,""},
qgen.DB_Table_Column{"issued_by","int",0,false,false,""},
qgen.DB_Table_Column{"issued_at","createdAt",0,false,false,""},
qgen.DB_Table_Column{"revert_at","datetime",0,false,false,""},
qgen.DB_Table_Column{"temporary","boolean",0,false,false,""}, // special case for permanent bans to do the necessary bookkeeping, might be removed in the future
qgen.DB_Table_Column{"uid", "int", 0, false, false, ""},
qgen.DB_Table_Column{"set_group", "int", 0, false, false, ""},
qgen.DB_Table_Column{"issued_by", "int", 0, false, false, ""},
qgen.DB_Table_Column{"issued_at", "createdAt", 0, false, false, ""},
qgen.DB_Table_Column{"revert_at", "datetime", 0, false, false, ""},
qgen.DB_Table_Column{"temporary", "boolean", 0, false, false, ""}, // special case for permanent bans to do the necessary bookkeeping, might be removed in the future
},
[]qgen.DB_Table_Key{
qgen.DB_Table_Key{"uid","primary"},
qgen.DB_Table_Key{"uid", "primary"},
},
)
qgen.Install.CreateTable("word_filters","","",
qgen.Install.CreateTable("word_filters", "", "",
[]qgen.DB_Table_Column{
qgen.DB_Table_Column{"wfid","int",0,false,true,""},
qgen.DB_Table_Column{"find","varchar",200,false,false,""},
qgen.DB_Table_Column{"replacement","varchar",200,false,false,""},
qgen.DB_Table_Column{"wfid", "int", 0, false, true, ""},
qgen.DB_Table_Column{"find", "varchar", 200, false, false, ""},
qgen.DB_Table_Column{"replacement", "varchar", 200, false, false, ""},
},
[]qgen.DB_Table_Key{
qgen.DB_Table_Key{"wfid","primary"},
qgen.DB_Table_Key{"wfid", "primary"},
},
)
qgen.Install.CreateTable("sync", "", "",
[]qgen.DB_Table_Column{
qgen.DB_Table_Column{"last_update", "datetime", 0, false, false, ""},
},
[]qgen.DB_Table_Key{},
)
return nil
}
// nolint
func seed_tables(adapter qgen.DB_Adapter) error {
return nil
}
// nolint
func write_selects(adapter qgen.DB_Adapter) error {
// 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 = ?", "", "")
// Looking for get_topic? Your statement is in another castle
adapter.SimpleSelect("get_reply","replies","tid, content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress, likeCount","rid = ?","","")
adapter.SimpleSelect("get_user_reply","users_replies","uid, content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress","rid = ?","","")
adapter.SimpleSelect("get_password","users","password,salt","uid = ?","","")
adapter.SimpleSelect("get_settings","settings","name, content, type","","","")
adapter.SimpleSelect("get_setting","settings","content, type","name = ?","","")
adapter.SimpleSelect("get_full_setting","settings","name, type, constraints","name = ?","","")
adapter.SimpleSelect("get_full_settings","settings","name, content, type, constraints","","","")
adapter.SimpleSelect("get_groups","users_groups","gid, name, permissions, plugin_perms, is_mod, is_admin, is_banned, tag","","","")
adapter.SimpleSelect("get_forums","forums","fid, name, desc, active, preset, parentID, parentType, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime","","fid ASC","")
adapter.SimpleSelect("get_forums_permissions","forums_permissions","gid, fid, permissions","","gid ASC, fid ASC","")
adapter.SimpleSelect("get_plugins","plugins","uname, active, installed","","","")
adapter.SimpleSelect("get_themes","themes","uname, default","","","")
adapter.SimpleSelect("get_widgets","widgets","position, side, type, active, location, data","","position ASC","")
adapter.SimpleSelect("is_plugin_active","plugins","active","uname = ?","","")
adapter.SimpleSelect("get_reply", "replies", "tid, content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress, likeCount", "rid = ?", "", "")
adapter.SimpleSelect("get_user_reply", "users_replies", "uid, content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress", "rid = ?", "", "")
adapter.SimpleSelect("get_password", "users", "password,salt", "uid = ?", "", "")
adapter.SimpleSelect("get_settings", "settings", "name, content, type", "", "", "")
adapter.SimpleSelect("get_setting", "settings", "content, type", "name = ?", "", "")
adapter.SimpleSelect("get_full_setting", "settings", "name, type, constraints", "name = ?", "", "")
adapter.SimpleSelect("get_full_settings", "settings", "name, content, type, constraints", "", "", "")
adapter.SimpleSelect("get_groups", "users_groups", "gid, name, permissions, plugin_perms, is_mod, is_admin, is_banned, tag", "", "", "")
adapter.SimpleSelect("get_forums", "forums", "fid, name, desc, active, preset, parentID, parentType, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime", "", "fid ASC", "")
adapter.SimpleSelect("get_forums_permissions", "forums_permissions", "gid, fid, permissions", "", "gid ASC, fid ASC", "")
adapter.SimpleSelect("get_plugins", "plugins", "uname, active, installed", "", "", "")
adapter.SimpleSelect("get_themes", "themes", "uname, default", "", "", "")
adapter.SimpleSelect("get_widgets", "widgets", "position, side, type, active, location, data", "", "position ASC", "")
adapter.SimpleSelect("is_plugin_active", "plugins", "active", "uname = ?", "", "")
//adapter.SimpleSelect("is_plugin_installed","plugins","installed","uname = ?","","")
adapter.SimpleSelect("get_users","users","uid, name, group, active, is_super_admin, avatar","","","")
adapter.SimpleSelect("get_users_offset","users","uid, name, group, active, is_super_admin, avatar","","","?,?")
adapter.SimpleSelect("get_word_filters","word_filters","wfid, find, replacement","","","")
adapter.SimpleSelect("is_theme_default","themes","default","uname = ?","","")
adapter.SimpleSelect("get_modlogs","moderation_logs","action, elementID, elementType, ipaddress, actorID, doneAt","","","")
adapter.SimpleSelect("get_modlogs_offset","moderation_logs","action, elementID, elementType, ipaddress, actorID, doneAt","","","?,?")
adapter.SimpleSelect("get_reply_tid","replies","tid","rid = ?","","")
adapter.SimpleSelect("get_topic_fid","topics","parentID","tid = ?","","")
adapter.SimpleSelect("get_user_reply_uid","users_replies","uid","rid = ?","","")
adapter.SimpleSelect("has_liked_topic","likes","targetItem","sentBy = ? and targetItem = ? and targetType = 'topics'","","")
adapter.SimpleSelect("has_liked_reply","likes","targetItem","sentBy = ? and targetItem = ? and targetType = 'replies'","","")
adapter.SimpleSelect("get_user_name","users","name","uid = ?","","")
adapter.SimpleSelect("get_user_active","users","active","uid = ?","","")
adapter.SimpleSelect("get_emails_by_user","emails","email, validated, token","uid = ?","","")
adapter.SimpleSelect("get_topic_basic","topics","title, content","tid = ?","","")
adapter.SimpleSelect("get_activity_entry","activity_stream","actor, targetUser, event, elementType, elementID","asid = ?","","")
adapter.SimpleSelect("forum_entry_exists","forums","fid","name = ''","fid ASC","0,1")
adapter.SimpleSelect("group_entry_exists","users_groups","gid","name = ''","gid ASC","0,1")
adapter.SimpleSelect("get_forum_topics_offset","topics","tid, title, content, createdBy, is_closed, sticky, createdAt, lastReplyAt, lastReplyBy, parentID, postCount, likeCount","parentID = ?","sticky DESC, lastReplyAt DESC, createdBy DESC","?,?")
adapter.SimpleSelect("get_expired_scheduled_groups","users_groups_scheduler","uid","UTC_TIMESTAMP() > revert_at AND temporary = 1","","")
adapter.SimpleSelect("get_users", "users", "uid, name, group, active, is_super_admin, avatar", "", "", "")
adapter.SimpleSelect("get_users_offset", "users", "uid, name, group, active, is_super_admin, avatar", "", "", "?,?")
adapter.SimpleSelect("get_word_filters", "word_filters", "wfid, find, replacement", "", "", "")
adapter.SimpleSelect("is_theme_default", "themes", "default", "uname = ?", "", "")
adapter.SimpleSelect("get_modlogs", "moderation_logs", "action, elementID, elementType, ipaddress, actorID, doneAt", "", "", "")
adapter.SimpleSelect("get_modlogs_offset", "moderation_logs", "action, elementID, elementType, ipaddress, actorID, doneAt", "", "", "?,?")
adapter.SimpleSelect("get_reply_tid", "replies", "tid", "rid = ?", "", "")
adapter.SimpleSelect("get_topic_fid", "topics", "parentID", "tid = ?", "", "")
adapter.SimpleSelect("get_user_reply_uid", "users_replies", "uid", "rid = ?", "", "")
adapter.SimpleSelect("has_liked_topic", "likes", "targetItem", "sentBy = ? and targetItem = ? and targetType = 'topics'", "", "")
adapter.SimpleSelect("has_liked_reply", "likes", "targetItem", "sentBy = ? and targetItem = ? and targetType = 'replies'", "", "")
adapter.SimpleSelect("get_user_name", "users", "name", "uid = ?", "", "")
adapter.SimpleSelect("get_user_active", "users", "active", "uid = ?", "", "")
adapter.SimpleSelect("get_emails_by_user", "emails", "email, validated, token", "uid = ?", "", "")
adapter.SimpleSelect("get_topic_basic", "topics", "title, content", "tid = ?", "", "")
adapter.SimpleSelect("get_activity_entry", "activity_stream", "actor, targetUser, event, elementType, elementID", "asid = ?", "", "")
adapter.SimpleSelect("forum_entry_exists", "forums", "fid", "name = ''", "fid ASC", "0,1")
adapter.SimpleSelect("group_entry_exists", "users_groups", "gid", "name = ''", "gid ASC", "0,1")
adapter.SimpleSelect("get_forum_topics_offset", "topics", "tid, title, content, createdBy, is_closed, sticky, createdAt, lastReplyAt, lastReplyBy, parentID, postCount, likeCount", "parentID = ?", "sticky DESC, lastReplyAt DESC, createdBy DESC", "?,?")
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
}
// nolint
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_list","topics","users","topics.tid, topics.title, topics.content, topics.createdBy, topics.is_closed, topics.sticky, topics.createdAt, topics.parentID, users.name, users.avatar","topics.createdBy = users.uid","","topics.sticky DESC, topics.lastReplyAt DESC, topics.createdBy DESC","")
adapter.SimpleLeftJoin("get_topic_user","topics","users","topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.url_prefix, users.url_name, users.level","topics.createdBy = users.uid","tid = ?","","")
adapter.SimpleLeftJoin("get_topic_by_reply","replies","topics","topics.tid, topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, topics.data","replies.tid = topics.tid","rid = ?","","")
adapter.SimpleLeftJoin("get_topic_replies","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.createdBy = users.uid","tid = ?","","")
adapter.SimpleLeftJoin("get_forum_topics","topics","users","topics.tid, topics.title, topics.content, topics.createdBy, topics.is_closed, topics.sticky, topics.createdAt, topics.lastReplyAt, topics.parentID, users.name, users.avatar","topics.createdBy = users.uid","topics.parentID = ?","topics.sticky DESC, topics.lastReplyAt DESC, topics.createdBy desc","")
adapter.SimpleLeftJoin("get_profile_replies","users_replies","users","users_replies.rid, users_replies.content, users_replies.createdBy, users_replies.createdAt, users_replies.lastEdit, users_replies.lastEditBy, users.avatar, users.name, users.group","users_replies.createdBy = users.uid","users_replies.uid = ?","","")
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_list", "topics", "users", "topics.tid, topics.title, topics.content, topics.createdBy, topics.is_closed, topics.sticky, topics.createdAt, topics.parentID, users.name, users.avatar", "topics.createdBy = users.uid", "", "topics.sticky DESC, topics.lastReplyAt DESC, topics.createdBy DESC", "")
adapter.SimpleLeftJoin("get_topic_user", "topics", "users", "topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.url_prefix, users.url_name, users.level", "topics.createdBy = users.uid", "tid = ?", "", "")
adapter.SimpleLeftJoin("get_topic_by_reply", "replies", "topics", "topics.tid, topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, topics.data", "replies.tid = topics.tid", "rid = ?", "", "")
adapter.SimpleLeftJoin("get_topic_replies", "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.createdBy = users.uid", "tid = ?", "", "")
adapter.SimpleLeftJoin("get_forum_topics", "topics", "users", "topics.tid, topics.title, topics.content, topics.createdBy, topics.is_closed, topics.sticky, topics.createdAt, topics.lastReplyAt, topics.parentID, users.name, users.avatar", "topics.createdBy = users.uid", "topics.parentID = ?", "topics.sticky DESC, topics.lastReplyAt DESC, topics.createdBy desc", "")
adapter.SimpleLeftJoin("get_profile_replies", "users_replies", "users", "users_replies.rid, users_replies.content, users_replies.createdBy, users_replies.createdAt, users_replies.lastEdit, users_replies.lastEditBy, users.avatar, users.name, users.group", "users_replies.createdBy = users.uid", "users_replies.uid = ?", "", "")
return nil
}
// nolint
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
}
// nolint
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_report","topics","title,content,parsed_content,createdAt,lastReplyAt,createdBy,data,parentID,css_class","?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,1,'report'")
adapter.SimpleInsert("create_topic", "topics", "parentID,title,content,parsed_content,createdAt,lastReplyAt,lastReplyBy,ipaddress,words,createdBy", "?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,?")
adapter.SimpleInsert("create_reply","replies","tid,content,parsed_content,createdAt,ipaddress,words,createdBy","?,?,?,UTC_TIMESTAMP(),?,?,?")
adapter.SimpleInsert("create_action_reply","replies","tid,actionType,ipaddress,createdBy","?,?,?,?")
adapter.SimpleInsert("create_like","likes","weight, targetItem, targetType, sentBy","?,?,?,?")
adapter.SimpleInsert("add_activity","activity_stream","actor,targetUser,event,elementType,elementID","?,?,?,?,?")
adapter.SimpleInsert("notify_one","activity_stream_matches","watcher,asid","?,?")
adapter.SimpleInsert("add_email","emails","email, uid, validated, token","?,?,?,?")
adapter.SimpleInsert("create_profile_reply","users_replies","uid, content, parsed_content, createdAt, createdBy, ipaddress","?,?,?,UTC_TIMESTAMP(),?,?")
adapter.SimpleInsert("add_subscription","activity_subscriptions","user,targetID,targetType,level","?,?,?,2")
adapter.SimpleInsert("create_forum","forums","name, desc, active, preset","?,?,?,?")
adapter.SimpleInsert("add_forum_perms_to_forum","forums_permissions","gid,fid,preset,permissions","?,?,?,?")
adapter.SimpleInsert("add_plugin","plugins","uname, active, installed","?,?,?")
adapter.SimpleInsert("add_theme","themes","uname,default","?,?")
adapter.SimpleInsert("create_report", "topics", "title,content,parsed_content,createdAt,lastReplyAt,createdBy,data,parentID,css_class", "?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,1,'report'")
adapter.SimpleInsert("create_reply", "replies", "tid,content,parsed_content,createdAt,ipaddress,words,createdBy", "?,?,?,UTC_TIMESTAMP(),?,?,?")
adapter.SimpleInsert("create_action_reply", "replies", "tid,actionType,ipaddress,createdBy", "?,?,?,?")
adapter.SimpleInsert("create_like", "likes", "weight, targetItem, targetType, sentBy", "?,?,?,?")
adapter.SimpleInsert("add_activity", "activity_stream", "actor,targetUser,event,elementType,elementID", "?,?,?,?,?")
adapter.SimpleInsert("notify_one", "activity_stream_matches", "watcher,asid", "?,?")
adapter.SimpleInsert("add_email", "emails", "email, uid, validated, token", "?,?,?,?")
adapter.SimpleInsert("create_profile_reply", "users_replies", "uid, content, parsed_content, createdAt, createdBy, ipaddress", "?,?,?,UTC_TIMESTAMP(),?,?")
adapter.SimpleInsert("add_subscription", "activity_subscriptions", "user,targetID,targetType,level", "?,?,?,2")
adapter.SimpleInsert("create_forum", "forums", "name, desc, active, preset", "?,?,?,?")
adapter.SimpleInsert("add_forum_perms_to_forum", "forums_permissions", "gid,fid,preset,permissions", "?,?,?,?")
adapter.SimpleInsert("add_plugin", "plugins", "uname, active, installed", "?,?,?")
adapter.SimpleInsert("add_theme", "themes", "uname,default", "?,?")
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_adminlog_entry", "administration_logs", "action, elementID, elementType, ipaddress, actorID, doneAt", "?,?,?,?,?,UTC_TIMESTAMP()")
adapter.SimpleInsert("create_word_filter", "word_filters", "find, replacement", "?,?")
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_adminlog_entry","administration_logs","action, elementID, elementType, ipaddress, actorID, doneAt","?,?,?,?,?,UTC_TIMESTAMP()")
adapter.SimpleInsert("create_word_filter","word_filters","find, replacement","?,?")
return nil
}
// nolint
func write_replaces(adapter qgen.DB_Adapter) error {
adapter.SimpleReplace("add_forum_perms_to_group","forums_permissions","gid,fid,preset,permissions","?,?,?,?")
adapter.SimpleReplace("replace_schedule_group","users_groups_scheduler","uid, set_group, issued_by, issued_at, revert_at, temporary","?,?,?,UTC_TIMESTAMP(),?,?")
adapter.SimpleReplace("add_forum_perms_to_group", "forums_permissions", "gid,fid,preset,permissions", "?,?,?,?")
adapter.SimpleReplace("replace_schedule_group", "users_groups_scheduler", "uid, set_group, issued_by, issued_at, revert_at, temporary", "?,?,?,UTC_TIMESTAMP(),?,?")
return nil
}
// nolint
func write_updates(adapter qgen.DB_Adapter) error {
adapter.SimpleUpdate("add_replies_to_topic","topics","postCount = postCount + ?, lastReplyBy = ?, lastReplyAt = UTC_TIMESTAMP()","tid = ?")
adapter.SimpleUpdate("remove_replies_from_topic","topics","postCount = postCount - ?","tid = ?")
adapter.SimpleUpdate("add_topics_to_forum","forums","topicCount = topicCount + ?","fid = ?")
adapter.SimpleUpdate("remove_topics_from_forum","forums","topicCount = topicCount - ?","fid = ?")
adapter.SimpleUpdate("update_forum_cache","forums","lastTopic = ?, lastTopicID = ?, lastReplyer = ?, lastReplyerID = ?, lastTopicTime = UTC_TIMESTAMP()","fid = ?")
adapter.SimpleUpdate("add_replies_to_topic", "topics", "postCount = postCount + ?, lastReplyBy = ?, lastReplyAt = UTC_TIMESTAMP()", "tid = ?")
adapter.SimpleUpdate("add_likes_to_topic","topics","likeCount = likeCount + ?","tid = ?")
adapter.SimpleUpdate("add_likes_to_reply","replies","likeCount = likeCount + ?","rid = ?")
adapter.SimpleUpdate("edit_topic","topics","title = ?, content = ?, parsed_content = ?, is_closed = ?","tid = ?")
adapter.SimpleUpdate("edit_reply","replies","content = ?, parsed_content = ?","rid = ?")
adapter.SimpleUpdate("stick_topic","topics","sticky = 1","tid = ?")
adapter.SimpleUpdate("unstick_topic","topics","sticky = 0","tid = ?")
adapter.SimpleUpdate("update_last_ip","users","last_ip = ?","uid = ?")
adapter.SimpleUpdate("remove_replies_from_topic", "topics", "postCount = postCount - ?", "tid = ?")
adapter.SimpleUpdate("update_session","users","session = ?","uid = ?")
adapter.SimpleUpdate("add_topics_to_forum", "forums", "topicCount = topicCount + ?", "fid = ?")
adapter.SimpleUpdate("set_password","users","password = ?, salt = ?","uid = ?")
adapter.SimpleUpdate("set_avatar","users","avatar = ?","uid = ?")
adapter.SimpleUpdate("set_username","users","name = ?","uid = ?")
adapter.SimpleUpdate("change_group","users","group = ?","uid = ?")
adapter.SimpleUpdate("activate_user","users","active = 1","uid = ?")
adapter.SimpleUpdate("update_user_level","users","level = ?","uid = ?")
adapter.SimpleUpdate("increment_user_score","users","score = score + ?","uid = ?")
adapter.SimpleUpdate("increment_user_posts","users","posts = posts + ?","uid = ?")
adapter.SimpleUpdate("increment_user_bigposts","users","posts = posts + ?, bigposts = bigposts + ?","uid = ?")
adapter.SimpleUpdate("increment_user_megaposts","users","posts = posts + ?, bigposts = bigposts + ?, megaposts = megaposts + ?","uid = ?")
adapter.SimpleUpdate("increment_user_topics","users","topics = topics + ?","uid = ?")
adapter.SimpleUpdate("remove_topics_from_forum", "forums", "topicCount = topicCount - ?", "fid = ?")
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_setting","settings","content = ?","name = ?")
adapter.SimpleUpdate("update_plugin","plugins","active = ?","uname = ?")
adapter.SimpleUpdate("update_plugin_install","plugins","installed = ?","uname = ?")
adapter.SimpleUpdate("update_theme","themes","default = ?","uname = ?")
adapter.SimpleUpdate("update_user","users","name = ?, email = ?, group = ?","uid = ?")
adapter.SimpleUpdate("update_forum_cache", "forums", "lastTopic = ?, lastTopicID = ?, lastReplyer = ?, lastReplyerID = ?, lastTopicTime = UTC_TIMESTAMP()", "fid = ?")
adapter.SimpleUpdate("add_likes_to_topic", "topics", "likeCount = likeCount + ?", "tid = ?")
adapter.SimpleUpdate("add_likes_to_reply", "replies", "likeCount = likeCount + ?", "rid = ?")
adapter.SimpleUpdate("edit_topic", "topics", "title = ?, content = ?, parsed_content = ?, is_closed = ?", "tid = ?")
adapter.SimpleUpdate("edit_reply", "replies", "content = ?, parsed_content = ?", "rid = ?")
adapter.SimpleUpdate("stick_topic", "topics", "sticky = 1", "tid = ?")
adapter.SimpleUpdate("unstick_topic", "topics", "sticky = 0", "tid = ?")
adapter.SimpleUpdate("update_last_ip", "users", "last_ip = ?", "uid = ?")
adapter.SimpleUpdate("update_session", "users", "session = ?", "uid = ?")
adapter.SimpleUpdate("set_password", "users", "password = ?, salt = ?", "uid = ?")
adapter.SimpleUpdate("set_avatar", "users", "avatar = ?", "uid = ?")
adapter.SimpleUpdate("set_username", "users", "name = ?", "uid = ?")
adapter.SimpleUpdate("change_group", "users", "group = ?", "uid = ?")
adapter.SimpleUpdate("activate_user", "users", "active = 1", "uid = ?")
adapter.SimpleUpdate("update_user_level", "users", "level = ?", "uid = ?")
adapter.SimpleUpdate("increment_user_score", "users", "score = score + ?", "uid = ?")
adapter.SimpleUpdate("increment_user_posts", "users", "posts = posts + ?", "uid = ?")
adapter.SimpleUpdate("increment_user_bigposts", "users", "posts = posts + ?, bigposts = bigposts + ?", "uid = ?")
adapter.SimpleUpdate("increment_user_megaposts", "users", "posts = posts + ?, bigposts = bigposts + ?, megaposts = megaposts + ?", "uid = ?")
adapter.SimpleUpdate("increment_user_topics", "users", "topics = topics + ?", "uid = ?")
adapter.SimpleUpdate("edit_profile_reply", "users_replies", "content = ?, parsed_content = ?", "rid = ?")
adapter.SimpleUpdate("update_forum", "forums", "name = ?, desc = ?, active = ?, preset = ?", "fid = ?")
adapter.SimpleUpdate("update_setting", "settings", "content = ?", "name = ?")
adapter.SimpleUpdate("update_plugin", "plugins", "active = ?", "uname = ?")
adapter.SimpleUpdate("update_plugin_install", "plugins", "installed = ?", "uname = ?")
adapter.SimpleUpdate("update_theme", "themes", "default = ?", "uname = ?")
adapter.SimpleUpdate("update_user", "users", "name = ?, email = ?, group = ?", "uid = ?")
adapter.SimpleUpdate("update_group_perms", "users_groups", "permissions = ?", "gid = ?")
adapter.SimpleUpdate("update_group_rank", "users_groups", "is_admin = ?, is_mod = ?, is_banned = ?", "gid = ?")
adapter.SimpleUpdate("update_group", "users_groups", "name = ?, tag = ?", "gid = ?")
adapter.SimpleUpdate("update_email", "emails", "email = ?, uid = ?, validated = ?, token = ?", "email = ?")
adapter.SimpleUpdate("verify_email", "emails", "validated = 1, token = ''", "email = ?") // Need to fix this: Empty string isn't working, it gets set to 1 instead x.x -- Has this been fixed?
adapter.SimpleUpdate("set_temp_group", "users", "temp_group = ?", "uid = ?")
adapter.SimpleUpdate("update_word_filter", "word_filters", "find = ?, replacement = ?", "wfid = ?")
adapter.SimpleUpdate("bump_sync", "sync", "last_update = UTC_TIMESTAMP()", "")
adapter.SimpleUpdate("update_group_perms","users_groups","permissions = ?","gid = ?")
adapter.SimpleUpdate("update_group_rank","users_groups","is_admin = ?, is_mod = ?, is_banned = ?","gid = ?")
adapter.SimpleUpdate("update_group","users_groups","name = ?, tag = ?","gid = ?")
adapter.SimpleUpdate("update_email","emails","email = ?, uid = ?, validated = ?, token = ?","email = ?")
adapter.SimpleUpdate("verify_email","emails","validated = 1, token = ''","email = ?") // Need to fix this: Empty string isn't working, it gets set to 1 instead x.x -- Has this been fixed?
adapter.SimpleUpdate("set_temp_group","users","temp_group = ?","uid = ?")
adapter.SimpleUpdate("update_word_filter","word_filters","find = ?, replacement = ?","wfid = ?")
return nil
}
// nolint
func write_deletes(adapter qgen.DB_Adapter) error {
adapter.SimpleDelete("delete_reply","replies","rid = ?")
adapter.SimpleDelete("delete_topic","topics","tid = ?")
adapter.SimpleDelete("delete_profile_reply","users_replies","rid = ?")
adapter.SimpleDelete("delete_forum_perms_by_forum","forums_permissions","fid = ?")
adapter.SimpleDelete("delete_activity_stream_match","activity_stream_matches","watcher = ? AND asid = ?")
adapter.SimpleDelete("delete_reply", "replies", "rid = ?")
adapter.SimpleDelete("delete_topic", "topics", "tid = ?")
adapter.SimpleDelete("delete_profile_reply", "users_replies", "rid = ?")
adapter.SimpleDelete("delete_forum_perms_by_forum", "forums_permissions", "fid = ?")
adapter.SimpleDelete("delete_activity_stream_match", "activity_stream_matches", "watcher = ? AND asid = ?")
//adapter.SimpleDelete("delete_activity_stream_matches_by_watcher","activity_stream_matches","watcher = ?")
adapter.SimpleDelete("delete_word_filter","word_filters","wfid = ?")
adapter.SimpleDelete("delete_word_filter", "word_filters", "wfid = ?")
return nil
}
// nolint
func write_simple_counts(adapter qgen.DB_Adapter) error {
adapter.SimpleCount("report_exists","topics","data = ? AND data != '' AND parentID = 1","")
adapter.SimpleCount("group_count","users_groups","","")
adapter.SimpleCount("modlog_count","moderation_logs","","")
adapter.SimpleCount("report_exists", "topics", "data = ? AND data != '' AND parentID = 1", "")
adapter.SimpleCount("group_count", "users_groups", "", "")
adapter.SimpleCount("modlog_count", "moderation_logs", "", "")
return nil
}
// nolint
func write_insert_selects(adapter qgen.DB_Adapter) error {
adapter.SimpleInsertSelect("add_forum_perms_to_forum_admins",
qgen.DB_Insert{"forums_permissions","gid,fid,preset,permissions",""},
qgen.DB_Select{"users_groups","gid, ? AS fid, ? AS preset, ? AS permissions","is_admin = 1","",""},
qgen.DB_Insert{"forums_permissions", "gid,fid,preset,permissions", ""},
qgen.DB_Select{"users_groups", "gid, ? AS fid, ? AS preset, ? AS permissions", "is_admin = 1", "", ""},
)
adapter.SimpleInsertSelect("add_forum_perms_to_forum_staff",
qgen.DB_Insert{"forums_permissions","gid,fid,preset,permissions",""},
qgen.DB_Select{"users_groups","gid, ? AS fid, ? AS preset, ? AS permissions","is_admin = 0 AND is_mod = 1","",""},
qgen.DB_Insert{"forums_permissions", "gid,fid,preset,permissions", ""},
qgen.DB_Select{"users_groups", "gid, ? AS fid, ? AS preset, ? AS permissions", "is_admin = 0 AND is_mod = 1", "", ""},
)
adapter.SimpleInsertSelect("add_forum_perms_to_forum_members",
qgen.DB_Insert{"forums_permissions","gid,fid,preset,permissions",""},
qgen.DB_Select{"users_groups","gid, ? AS fid, ? AS preset, ? AS permissions","is_admin = 0 AND is_mod = 0 AND is_banned = 0","",""},
qgen.DB_Insert{"forums_permissions", "gid,fid,preset,permissions", ""},
qgen.DB_Select{"users_groups", "gid, ? AS fid, ? AS preset, ? AS permissions", "is_admin = 0 AND is_mod = 0 AND is_banned = 0", "", ""},
)
return nil
}
// nolint
func write_insert_left_joins(adapter qgen.DB_Adapter) error {
return nil
}
// nolint
func write_insert_inner_joins(adapter qgen.DB_Adapter) error {
adapter.SimpleInsertInnerJoin("notify_watchers",
qgen.DB_Insert{"activity_stream_matches","watcher, asid",""},
qgen.DB_Join{"activity_stream","activity_subscriptions","activity_subscriptions.user, activity_stream.asid","activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor","asid = ?","",""},
qgen.DB_Insert{"activity_stream_matches", "watcher, asid", ""},
qgen.DB_Join{"activity_stream", "activity_subscriptions", "activity_subscriptions.user, activity_stream.asid", "activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor", "asid = ?", "", ""},
)
return nil
}

View File

@ -6,30 +6,35 @@ import "strings"
import "sync"
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 {
sync.RWMutex
routes map[string]func(http.ResponseWriter, *http.Request)
}
// nolint
func NewRouter() *Router {
return &Router{
routes: make(map[string]func(http.ResponseWriter, *http.Request)),
}
}
// nolint
func (router *Router) Handle(pattern string, handle http.Handler) {
router.Lock()
router.routes[pattern] = handle.ServeHTTP
router.Unlock()
}
// nolint
func (router *Router) HandleFunc(pattern string, handle func(http.ResponseWriter, *http.Request)) {
router.Lock()
router.routes[pattern] = handle
router.Unlock()
}
// nolint
func (router *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' {
w.WriteHeader(405)
@ -37,10 +42,10 @@ func (router *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
var /*extra_data, */prefix string
if req.URL.Path[len(req.URL.Path) - 1] != '/' {
//extra_data = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:]
prefix = req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1]
var /*extraData, */ prefix string
if req.URL.Path[len(req.URL.Path)-1] != '/' {
//extraData = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:]
prefix = req.URL.Path[:strings.LastIndexByte(req.URL.Path, '/')+1]
} else {
prefix = req.URL.Path
}
@ -50,9 +55,9 @@ func (router *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
router.RUnlock()
if ok {
handle(w,req)
handle(w, req)
return
}
//log.Print("req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/')]",req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/')])
NotFound(w,req)
NotFound(w, req)
}

View File

@ -2,6 +2,7 @@
package main
import "log"
//import "strings"
import "os"
@ -10,13 +11,13 @@ var route_groups []RouteGroup
func main() {
log.Println("Generating the router...")
// Load all the routes...
routes()
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 {
var end int
if route.Path[len(route.Path)-1] == '/' {
@ -34,7 +35,7 @@ func main() {
}
out += ")\n\t\t\treturn"
}
for _, group := range route_groups {
var end int
if group.Path[len(group.Path)-1] == '/' {
@ -51,7 +52,7 @@ func main() {
default_route = route
continue
}
out += "\n\t\t\t\tcase \"" + route.Path + "\":"
if route.Before != "" {
out += "\n\t\t\t\t\t" + route.Before
@ -62,7 +63,7 @@ func main() {
}
out += ")\n\t\t\t\t\treturn"
}
if default_route.Name != "" {
out += "\n\t\t\t\tdefault:"
if default_route.Before != "" {
@ -76,7 +77,7 @@ func main() {
}
out += "\n\t\t\t}"
}
fdata += `package main
import "log"
@ -142,9 +143,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if dev.SuperDebug {
log.Print("before route_static")
log.Print("prefix:", prefix)
log.Print("req.URL.Path:", req.URL.Path)
log.Print("extra_data:", extra_data)
log.Print("prefix: ", prefix)
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("extra_data: ", extra_data)
log.Print("req.Referer(): ", req.Referer())
}
if prefix == "/static" {
@ -178,7 +180,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
case "":
// 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) {
case "robots.txt":
route_robots_txt(w,req)
@ -207,7 +209,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
NotFound(w,req)
}
`
write_file("./gen_router.go",fdata)
write_file("./gen_router.go", fdata)
log.Println("Successfully generated the router")
}

View File

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

178
routes.go
View File

@ -35,10 +35,10 @@ func init() {
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
if len(req.URL.RawQuery) > 0 {
dest += "?" + req.URL.RawQuery
@ -54,13 +54,14 @@ func route_static(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
return
}
h := w.Header()
// 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)
return
}
h := w.Header()
h.Set("Last-Modified", file.FormattedModTime)
h.Set("Content-Type", file.Mimetype)
//Cache-Control: max-age=31536000
@ -68,7 +69,7 @@ func route_static(w http.ResponseWriter, r *http.Request) {
h.Set("Vary", "Accept-Encoding")
//http.ServeContent(w,r,r.URL.Path,file.Info.ModTime(),file)
//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-Length", strconv.FormatInt(file.GzipLength, 10))
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)
}*/
// TO-DO: Make this a static file somehow? Is it possible for us to put this file somewhere else?
// TO-DO: Add a sitemap
// TO-DO: Add an API so that plugins can register disallowed areas. E.g. /groups/join for plugin_socialgroups
// TODO: Make this a static file somehow? Is it possible for us to put this file somewhere else?
// TODO: Add a sitemap
// 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) {
_, _ = w.Write([]byte(`User-agent: *
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) {
headerVars, ok := SessionCheck(w, r, &user)
if !ok {
@ -232,7 +233,7 @@ func route_topics(w http.ResponseWriter, r *http.Request, user User) {
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)
if err != nil {
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
// 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 {
topicItem.Creator = userList[topicItem.CreatedBy]
topicItem.LastUser = userList[topicItem.LastReplyBy]
@ -279,7 +280,7 @@ func route_forum(w http.ResponseWriter, r *http.Request, user User, sfid string)
return
}
// TO-DO: Fix this double-check
// TODO: Fix this double-check
forum, err := fstore.CascadeGet(fid)
if err == ErrNoRows {
NotFound(w, r)
@ -309,7 +310,7 @@ func route_forum(w http.ResponseWriter, r *http.Request, user User, sfid string)
}
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 reqUserList = make(map[int]bool)
for rows.Next() {
@ -347,7 +348,7 @@ func route_forum(w http.ResponseWriter, r *http.Request, user User, sfid string)
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)
if err != nil {
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
// 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 {
topicItem.Creator = userList[topicItem.CreatedBy]
topicItem.LastUser = userList[topicItem.LastReplyBy]
@ -381,7 +382,7 @@ func route_forums(w http.ResponseWriter, r *http.Request, user User) {
var forumList []Forum
var canSee []int
if user.IsSuperAdmin {
canSee, err = fstore.GetAllIDs()
canSee, err = fstore.GetAllVisibleIDs()
if err != nil {
InternalError(err, w)
return
@ -396,7 +397,7 @@ func route_forums(w http.ResponseWriter, r *http.Request, user User) {
for _, fid := range canSee {
//log.Print(forums[fid])
var forum = *fstore.DirtyGet(fid)
if forum.Active && forum.Name != "" && forum.ParentID == 0 {
if forum.ParentID == 0 {
if forum.LastTopicID != 0 {
forum.LastTopicTime, err = relativeTime(forum.LastTopicTime)
if err != nil {
@ -582,7 +583,7 @@ func route_topic_id(w http.ResponseWriter, r *http.Request, user User) {
}
replyItem.Liked = false
// TO-DO: Rename this to topic_rrow_assign
// TODO: Rename this to topic_rrow_assign
if hooks["rrow_assign"] != nil {
runHook("rrow_assign", &replyItem)
}
@ -682,7 +683,7 @@ func route_profile(w http.ResponseWriter, r *http.Request, user User) {
replyLiked := false
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, "", ""})
}
@ -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)
}
// TODO: Re-add support for plugin_socialgroups
var forumList []Forum
var canSee []int
if user.IsSuperAdmin {
canSee, err = fstore.GetAllIDs()
canSee, err = fstore.GetAllVisibleIDs()
if err != nil {
InternalError(err, w)
return
@ -744,25 +746,23 @@ func route_topic_create(w http.ResponseWriter, r *http.Request, user User, sfid
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 {
// 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 {
continue
}
// Do a bulk forum fetch, just in case it's the SqlForumStore?
forum := fstore.DirtyGet(ffid)
if forum.Active && forum.Name != "" {
fcopy := *forum
if hooks["topic_create_frow_assign"] != nil {
// TO-DO: Add the skip feature to all the other row based hooks?
if runHook("topic_create_frow_assign", &fcopy).(bool) {
continue
}
fcopy := *forum
if hooks["topic_create_frow_assign"] != nil {
// TODO: Add the skip feature to all the other row based hooks?
if runHook("topic_create_frow_assign", &fcopy).(bool) {
continue
}
forumList = append(forumList, fcopy)
}
forumList = append(forumList, fcopy)
}
ctpage := CreateTopicPage{"Create Topic", user, headerVars, forumList, fid}
@ -789,7 +789,7 @@ func route_topic_create_submit(w http.ResponseWriter, r *http.Request, user User
return
}
// TO-DO: Add hooks to make use of headerLite
// TODO: Add hooks to make use of headerLite
_, ok := SimpleForumSessionCheck(w, r, &user, fid)
if !ok {
return
@ -799,7 +799,7 @@ func route_topic_create_submit(w http.ResponseWriter, r *http.Request, user User
return
}
topic_name := html.EscapeString(r.PostFormValue("topic-name"))
topicName := html.EscapeString(r.PostFormValue("topic-name"))
content := html.EscapeString(preparseMessage(r.PostFormValue("topic-content")))
ipaddress, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
@ -808,7 +808,7 @@ func route_topic_create_submit(w http.ResponseWriter, r *http.Request, user User
}
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 {
InternalError(err, w)
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)
err = increase_post_user_stats(wcount, user.ID, true, user)
err = user.increasePostStats(wcount, true)
if err != nil {
InternalError(err, w)
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 {
InternalError(err, w)
}
@ -865,7 +865,7 @@ func route_create_reply(w http.ResponseWriter, r *http.Request, user User) {
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)
if !ok {
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)
err = increase_post_user_stats(wcount, user.ID, false, user)
err = user.increasePostStats(wcount, false)
if err != nil {
InternalError(err, w)
return
@ -962,7 +962,7 @@ func route_like_topic(w http.ResponseWriter, r *http.Request, user User) {
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)
if !ok {
return
@ -1073,7 +1073,7 @@ func route_reply_like_submit(w http.ResponseWriter, r *http.Request, user User)
return
}
// TO-DO: Add hooks to make use of headerLite
// TODO: Add hooks to make use of headerLite
_, ok := SimpleForumSessionCheck(w, r, &user, fid)
if !ok {
return
@ -1171,8 +1171,8 @@ func route_profile_reply_create(w http.ResponseWriter, r *http.Request, user Use
return
}
var user_name string
err = get_user_name_stmt.QueryRow(uid).Scan(&user_name)
var userName string
err = get_user_name_stmt.QueryRow(uid).Scan(&userName)
if err == ErrNoRows {
LocalError("The profile you're trying to post on doesn't exist.", w, r, user)
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)
}
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 {
LoginRequired(w, r, user)
return
@ -1204,18 +1204,18 @@ func route_report_submit(w http.ResponseWriter, r *http.Request, user User, site
return
}
item_id, err := strconv.Atoi(sitem_id)
itemID, err := strconv.Atoi(sitemID)
if err != nil {
LocalError("Bad ID", w, r, user)
return
}
item_type := r.FormValue("type")
itemType := r.FormValue("type")
var fid int = 1
var fid = 1
var title, content string
if item_type == "reply" {
reply, err := getReply(item_id)
if itemType == "reply" {
reply, err := getReply(itemID)
if err == ErrNoRows {
LocalError("We were unable to find the reported post", w, r, user)
return
@ -1234,9 +1234,9 @@ func route_report_submit(w http.ResponseWriter, r *http.Request, user User, site
}
title = "Reply: " + topic.Title
content = reply.Content + "\n\nOriginal Post: #rid-" + strconv.Itoa(item_id)
} else if item_type == "user-reply" {
userReply, err := getUserReply(item_id)
content = reply.Content + "\n\nOriginal Post: #rid-" + strconv.Itoa(itemID)
} else if itemType == "user-reply" {
userReply, err := getUserReply(itemID)
if err == ErrNoRows {
LocalError("We weren't able to find the reported post", w, r, user)
return
@ -1255,8 +1255,8 @@ func route_report_submit(w http.ResponseWriter, r *http.Request, user User, site
}
title = "Profile: " + title
content = userReply.Content + "\n\nOriginal Post: @" + strconv.Itoa(userReply.ParentID)
} else if item_type == "topic" {
err = get_topic_basic_stmt.QueryRow(item_id).Scan(&title, &content)
} else if itemType == "topic" {
err = get_topic_basic_stmt.QueryRow(itemID).Scan(&title, &content)
if err == ErrNoRows {
NotFound(w, r)
return
@ -1265,10 +1265,10 @@ func route_report_submit(w http.ResponseWriter, r *http.Request, user User, site
return
}
title = "Topic: " + title
content = content + "\n\nOriginal Post: #tid-" + strconv.Itoa(item_id)
content = content + "\n\nOriginal Post: #tid-" + strconv.Itoa(itemID)
} else {
if vhooks["report_preassign"] != nil {
runVhookNoreturn("report_preassign", &item_id, &item_type)
runVhookNoreturn("report_preassign", &itemID, &itemType)
return
}
// 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
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 {
InternalError(err, w)
return
@ -1295,7 +1295,7 @@ func route_report_submit(w http.ResponseWriter, r *http.Request, user User, site
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 {
InternalError(err, w)
return
@ -1546,14 +1546,15 @@ func route_account_own_edit_username_submit(w http.ResponseWriter, r *http.Reque
return
}
new_username := html.EscapeString(r.PostFormValue("account-new-username"))
_, err = set_username_stmt.Exec(new_username, strconv.Itoa(user.ID))
newUsername := html.EscapeString(r.PostFormValue("account-new-username"))
_, err = set_username_stmt.Exec(newUsername, strconv.Itoa(user.ID))
if err != nil {
LocalError("Unable to change the username. Does someone else already have this name?", w, r, user)
return
}
user.Name = new_username
// TODO: Use the reloaded data instead for the name?
user.Name = newUsername
err = users.Load(user.ID)
if err != nil {
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)
}
// TODO: Move this into member_routes.go
func route_logout(w http.ResponseWriter, r *http.Request, user User) {
if !user.Loggedin {
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)
}
// TO-DO: Log failed attempted logins?
// TO-DO: 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 failed attempted logins?
// TODO: Lock IPS out if they have too many failed attempts?
// 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) {
if user.Loggedin {
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
}
confirm_password := r.PostFormValue("confirm_password")
log.Print("Registration Attempt! Username: " + username)
confirmPassword := r.PostFormValue("confirm_password")
log.Print("Registration Attempt! Username: " + username) // TODO: Add controls over what is logged when?
// Do the two inputted passwords match..?
if password != confirm_password {
if password != confirmPassword {
LocalError("The two passwords don't match.", w, r, user)
return
}
@ -1898,8 +1900,48 @@ func route_register_submit(w http.ResponseWriter, r *http.Request, user User) {
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// TO-DO: We don't need support XML here to support sitemaps, we could handle those elsewhere
var phrase_login_alerts = []byte(`{"msgs":[{"msg":"Login to see your alerts","path":"/accounts/login"}]}`)
// TODO: Set the cookie domain
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) {
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
if !user.Loggedin {
w.Write(phrase_login_alerts)
w.Write(phraseLoginAlerts)
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() {
settingBox.Store(SettingBox(make(map[string]interface{})))
//settingBox.Store(make(map[string]interface{}))
}
func LoadSettings() error {
@ -34,8 +33,7 @@ func LoadSettings() error {
}
defer rows.Close()
sBox := settingBox.Load().(SettingBox)
//sBox := settingBox.Load().(map[string]interface{})
var sBox = SettingBox(make(map[string]interface{}))
var sname, scontent, stype, sconstraints string
for rows.Next() {
err = rows.Scan(&sname, &scontent, &stype, &sconstraints)
@ -56,10 +54,10 @@ func LoadSettings() error {
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 {
var err error
var ssBox map[string]interface{} = map[string]interface{}(sBox)
var ssBox = map[string]interface{}(sBox)
if stype == "bool" {
ssBox[sname] = (scontent == "1")
} else if stype == "int" {

64
site.go
View File

@ -2,61 +2,57 @@ package main
import "net/http"
var site *Site = &Site{Name:"Magical Fairy Land"}
var db_config DB_Config = DB_Config{Host:"localhost"}
var site = &Site{Name: "Magical Fairy Land"}
var db_config = DB_Config{Host: "localhost"}
var config Config
var dev DevConfig
type Site struct
{
Name string
Email string
Url string
Port string
EnableSsl bool
type Site struct {
Name string
Email string
Url string
Port string
EnableSsl bool
EnableEmails bool
HasProxy bool
HasProxy bool
}
type DB_Config struct
{
Host string
type DB_Config struct {
Host string
Username string
Password string
Dbname string
Port string
Dbname string
Port string
}
type Config struct
{
SslPrivkey string
type Config struct {
SslPrivkey string
SslFullchain string
MaxRequestSize int
CacheTopicUser int
UserCacheCapacity int
MaxRequestSize int
CacheTopicUser int
UserCacheCapacity int
TopicCacheCapacity int
SmtpServer string
SmtpServer string
SmtpUsername string
SmtpPassword string
SmtpPort string
SmtpPort string
DefaultRoute func(http.ResponseWriter, *http.Request, User)
DefaultGroup int
ActivationGroup int
StaffCss string
DefaultRoute func(http.ResponseWriter, *http.Request, User)
DefaultGroup int
ActivationGroup int
StaffCss string
UncategorisedForumVisible bool
MinifyTemplates bool
MultiServer bool
MinifyTemplates bool
MultiServer bool
Noavatar string
Noavatar string
ItemsPerPage int
}
type DevConfig struct
{
DebugMode bool
type DevConfig struct {
DebugMode bool
SuperDebug bool
Profiling bool
Profiling bool
}

View File

@ -2,6 +2,12 @@ package main
import "time"
var lastSync time.Time
func init() {
lastSync = time.Now()
}
func handleExpiredScheduledGroups() error {
rows, err := get_expired_scheduled_groups_stmt.Query()
if err != nil {
@ -27,3 +33,37 @@ func handleExpiredScheduledGroups() error {
}
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(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([]byte(string(tmpl_forum_vars.Header.Widgets.RightSidebar)))
w.Write([]byte(item.Name))
w.Write(footer_2)
}
if tmpl_forum_vars.Header.ThemeName == item.Name {
w.Write(footer_3)
}
w.Write(footer_4)
w.Write([]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(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([]byte(string(tmpl_forums_vars.Header.Widgets.RightSidebar)))
w.Write([]byte(item.Name))
w.Write(footer_2)
}
if tmpl_forums_vars.Header.ThemeName == item.Name {
w.Write(footer_3)
}
w.Write(footer_4)
w.Write([]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("")
// nolint
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 {
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_alt_handle func(TopicPage, http.ResponseWriter) = interpreted_topic_template
// nolint
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 {
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) {
mapping, ok := themes[defaultTheme].TemplatesMap["forum"]
mapping, ok := themes[defaultThemeBox.Load().(string)].TemplatesMap["forum"]
if !ok {
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) {
mapping, ok := themes[defaultTheme].TemplatesMap["forums"]
mapping, ok := themes[defaultThemeBox.Load().(string)].TemplatesMap["forums"]
if !ok {
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) {
mapping, ok := themes[defaultTheme].TemplatesMap["profile"]
mapping, ok := themes[defaultThemeBox.Load().(string)].TemplatesMap["profile"]
if !ok {
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) {
mapping, ok := themes[defaultTheme].TemplatesMap["create-topic"]
mapping, ok := themes[defaultThemeBox.Load().(string)].TemplatesMap["create-topic"]
if !ok {
mapping = "create-topic"
}
@ -77,12 +84,18 @@ var template_create_topic_handle func(CreateTopicPage, http.ResponseWriter) = fu
func compileTemplates() error {
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}
// 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}
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{
Site: site,
Settings: settingBox.Load().(SettingBox),
Themes: themes,
ThemeName: defaultThemeBox.Load().(string),
NoticeList: []string{"test"},
Stylesheets: []string{"panel"},
Scripts: []string{"whatever"},
@ -97,66 +110,64 @@ func compileTemplates() error {
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, "", ""})
var varList map[string]VarItem = make(map[string]VarItem)
var varList = make(map[string]VarItem)
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 {
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 {
return err
}
varList = make(map[string]VarItem)
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 {
return err
}
var forumList []Forum
forums, err := fstore.GetAll()
forums, err := fstore.GetAllVisible()
if err != nil {
return err
}
for _, forum := range forums {
if forum.Active {
forumList = append(forumList, *forum)
}
forumList = append(forumList, *forum)
}
varList = make(map[string]VarItem)
forums_page := ForumsPage{"Forum List", user, headerVars, forumList}
forums_tmpl, err := c.compileTemplate("forums.html", "templates/", "ForumsPage", forums_page, varList)
forumsPage := ForumsPage{"Forum List", user, headerVars, forumList}
forumsTmpl, err := c.compileTemplate("forums.html", "templates/", "ForumsPage", forumsPage, varList)
if err != nil {
return err
}
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"})
topics_page := TopicsPage{"Topic List", user, headerVars, topicsList}
topics_tmpl, err := c.compileTemplate("topics.html", "templates/", "TopicsPage", topics_page, varList)
topicsPage := TopicsPage{"Topic List", user, headerVars, topicsList}
topicsTmpl, err := c.compileTemplate("topics.html", "templates/", "TopicsPage", topicsPage, varList)
if err != nil {
return err
}
//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})
forum_item := 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}
forum_tmpl, err := c.compileTemplate("forum.html", "templates/", "ForumPage", forum_page, varList)
forumItem := Forum{1, "general", "General Forum", "Where the general stuff happens", true, "all", 0, "", 0, "", "", 0, "", 0, ""}
forumPage := ForumPage{"General Forum", user, headerVars, topicsList, forumItem, 1, 1}
forumTmpl, err := c.compileTemplate("forum.html", "templates/", "ForumPage", forumPage, varList)
if err != nil {
return err
}
log.Print("Writing the templates")
go writeTemplate("topic", topic_id_tmpl)
go writeTemplate("topic_alt", topic_id_alt_tmpl)
go writeTemplate("profile", profile_tmpl)
go writeTemplate("forums", forums_tmpl)
go writeTemplate("topics", topics_tmpl)
go writeTemplate("forum", forum_tmpl)
go writeTemplate("topic", topicIDTmpl)
go writeTemplate("topic_alt", topicIDAltTmpl)
go writeTemplate("profile", profileTmpl)
go writeTemplate("forums", forumsTmpl)
go writeTemplate("topics", topicsTmpl)
go writeTemplate("forum", forumTmpl)
go func() {
err := writeFile("./template_list.go", "package main\n\n// nolint\n"+c.FragOut)
if err != nil {
@ -180,66 +191,62 @@ func initTemplates() {
}
compileTemplates()
// TO-DO: Add support for 64-bit integers
// TO-DO: Add support for floats
// TODO: Add support for 64-bit integers
// TODO: Add support for floats
fmap := make(map[string]interface{})
fmap["add"] = func(left interface{}, right interface{}) interface{} {
var left_int int
var right_int int
var leftInt, rightInt int
switch left := left.(type) {
case uint, uint8, uint16, int, int32:
left_int = left.(int)
leftInt = left.(int)
}
switch right := right.(type) {
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{} {
var left_int int
var right_int int
var leftInt, rightInt int
switch left := left.(type) {
case uint, uint8, uint16, int, int32:
left_int = left.(int)
leftInt = left.(int)
}
switch right := right.(type) {
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{} {
var left_int int
var right_int int
var leftInt, rightInt int
switch left := left.(type) {
case uint, uint8, uint16, int, int32:
left_int = left.(int)
leftInt = left.(int)
}
switch right := right.(type) {
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{} {
var left_int int
var right_int int
var leftInt, rightInt int
switch left := left.(type) {
case uint, uint8, uint16, int, int32:
left_int = left.(int)
leftInt = left.(int)
}
switch right := right.(type) {
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 left_int / right_int
return leftInt / rightInt
}
// The interpreted templates...

View File

@ -245,11 +245,27 @@ var topic_91 = []byte(`
</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_2 = []byte(`</aside>`)
var footer_3 = []byte(`
var footer_1 = []byte(`<option val="`)
var footer_2 = []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>
</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">
`)
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 class="rowitem"><h1>Ban User</h1></div>
</div>
@ -675,23 +691,26 @@ var topics_19 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky
var topics_20 = []byte(`
</span>
</div>
<div class="rowitem topic_right passive datarow" style="`)
var topics_21 = []byte(`background-image: url(`)
var topics_22 = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`)
var topics_23 = []byte(`">
<div class="rowitem topic_right passive datarow `)
var topics_21 = []byte(`topic_sticky`)
var topics_22 = []byte(`topic_closed`)
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>
<a href="`)
var topics_24 = []byte(`" class="lastName" style="font-size: 14px;">`)
var topics_25 = []byte(`</a><br>
var topics_27 = []byte(`" class="lastName" style="font-size: 14px;">`)
var topics_28 = []byte(`</a><br>
<span class="rowsmall lastReplyAt">Last: `)
var topics_26 = []byte(`</span>
var topics_29 = []byte(`</span>
</span>
</div>
`)
var topics_27 = []byte(`<div class="rowitem passive">There aren't any topics yet.`)
var topics_28 = []byte(` <a href="/topics/create/">Start one?</a>`)
var topics_29 = []byte(`</div>`)
var topics_30 = []byte(`
var topics_30 = []byte(`<div class="rowitem passive">There aren't any topics yet.`)
var topics_31 = []byte(` <a href="/topics/create/">Start one?</a>`)
var topics_32 = []byte(`</div>`)
var topics_33 = []byte(`
</div>
</main>

View File

@ -158,10 +158,26 @@ w.Write(profile_41)
w.Write(profile_42)
w.Write(profile_43)
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([]byte(string(tmpl_profile_vars.Header.Widgets.RightSidebar)))
w.Write([]byte(item.Name))
w.Write(footer_2)
}
if tmpl_profile_vars.Header.ThemeName == item.Name {
w.Write(footer_3)
}
w.Write(footer_4)
w.Write([]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(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([]byte(string(tmpl_topic_vars.Header.Widgets.RightSidebar)))
w.Write([]byte(item.Name))
w.Write(footer_2)
}
if tmpl_topic_vars.Header.ThemeName == item.Name {
w.Write(footer_3)
}
w.Write(footer_4)
w.Write([]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(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([]byte(string(tmpl_topic_alt_vars.Header.Widgets.RightSidebar)))
w.Write([]byte(item.Name))
w.Write(footer_2)
}
if tmpl_topic_alt_vars.Header.ThemeName == item.Name {
w.Write(footer_3)
}
w.Write(footer_4)
w.Write([]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:
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main
import "strconv"
import "net/http"
import "strconv"
// nolint
func init() {
@ -115,32 +115,56 @@ if item.Sticky {
w.Write(topics_19)
}
w.Write(topics_20)
if item.LastUser.Avatar != "" {
if item.Sticky {
w.Write(topics_21)
w.Write([]byte(item.LastUser.Avatar))
} else {
if item.IsClosed {
w.Write(topics_22)
}
}
w.Write(topics_23)
w.Write([]byte(item.LastUser.Link))
if item.LastUser.Avatar != "" {
w.Write(topics_24)
w.Write([]byte(item.LastUser.Name))
w.Write([]byte(item.LastUser.Avatar))
w.Write(topics_25)
w.Write([]byte(item.LastReplyAt))
}
w.Write(topics_26)
}
} else {
w.Write([]byte(item.LastUser.Link))
w.Write(topics_27)
if tmpl_topics_vars.CurrentUser.Perms.CreateTopic {
w.Write([]byte(item.LastUser.Name))
w.Write(topics_28)
}
w.Write([]byte(item.LastReplyAt))
w.Write(topics_29)
}
} else {
w.Write(topics_30)
w.Write(footer_0)
if tmpl_topics_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_1)
w.Write([]byte(string(tmpl_topics_vars.Header.Widgets.RightSidebar)))
w.Write(footer_2)
if tmpl_topics_vars.CurrentUser.Perms.CreateTopic {
w.Write(topics_31)
}
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_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"
)
// TO-DO: Turn this file into a library
// TODO: Turn this file into a library
var ctemplates []string
var tmplPtrMap = make(map[string]interface{})
var textOverlapList = make(map[string]int)
@ -263,6 +263,11 @@ func (c *CTemplateSet) compileSwitch(varholder string, holdreflect reflect.Value
for _, key := range outVal.MapKeys() {
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 {
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 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?")
}
cur = cur.FieldByName(id)
if cur.Kind() == reflect.Interface {
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" {
varbit = "string(" + varbit + "." + id + ")"*/
//if cur.Kind() == reflect.String && cur.Type().Name() != "string" {
@ -795,7 +810,7 @@ func (c *CTemplateSet) compileIfVarsub(varname string, varholder string, templat
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 dev.SuperDebug {
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)
if err != nil {
log.Fatal(err)
@ -998,7 +1013,7 @@ func (c *CTemplateSet) compileCommand(*parse.CommandNode) (out string) {
panic("Uh oh! Something went wrong!")
}
// TO-DO: Write unit tests for this
// TODO: Write unit tests for this
func minify(data string) string {
data = strings.Replace(data, "\t", "", -1)
data = strings.Replace(data, "\v", "", -1)
@ -1008,30 +1023,30 @@ func minify(data string) string {
return data
}
// TO-DO: Strip comments
// TO-DO: Handle CSS nested in <style> tags?
// TO-DO: Write unit tests for this
// TODO: Strip comments
// TODO: Handle CSS nested in <style> tags?
// TODO: Write unit tests for this
func minifyHTML(data string) string {
return minify(data)
}
// TO-DO: Have static files use this
// TO-DO: Strip comments
// TO-DO: Convert the rgb()s to hex codes?
// TO-DO: Write unit tests for this
// TODO: Have static files use this
// TODO: Strip comments
// TODO: Convert the rgb()s to hex codes?
// TODO: Write unit tests for this
func minifyCSS(data string) string {
return minify(data)
}
// TO-DO: Convert this to three character hex strings whenever possible?
// TO-DO: Write unit tests for this
// TODO: Convert this to three character hex strings whenever possible?
// TODO: Write unit tests for this
// nolint
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)
}
/*
// 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) {
// Strip the # at the start
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/email/">Email</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>

View File

@ -5,7 +5,7 @@
<div class="rowitem"><h1>Emails</h1></div>
</div>
<div class="colstack_item">
<!-- TO-DO: Do we need this inline CSS? -->
<!-- TODO: Do we need this inline CSS? -->
{{range .ItemList}}
<div class="rowitem" style="font-weight: normal;">
<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>
{{if .Header.Widgets.RightSidebar}}<aside class="sidebar">{{.Header.Widgets.RightSidebar}}</aside>{{end}}
<div style="clear: both;"></div>

View File

@ -25,7 +25,7 @@
<span>
<a class="rowtopic" href="{{.Link}}">{{.Title}}</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 .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>{{end}}
</span>

View File

@ -24,7 +24,7 @@
</span>
</span>
<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>
</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 .Active}}<a href="/panel/plugins/deactivate/{{.UName}}?session={{$.CurrentUser.Session}}" class="panel_tag">Deactivate</a>
{{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}}
{{else}}<a href="/panel/plugins/activate/{{.UName}}?session={{$.CurrentUser.Session}}" class="panel_tag">Activate</a>{{end}}
</span>

View File

@ -26,12 +26,12 @@
<div id="profile_right_lane" class="colstack_right">
{{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 class="rowitem"><h1>Ban User</h1></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;">
{{/** 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="colstack_item">
<div class="formrow real_first_child">
@ -100,7 +100,7 @@
</div>
{{/** Quick subpage switcher **/}}
{{/** TO-DO: Stop inlining this **/}}
{{/** TODO: Stop inlining this **/}}
<script type="text/javascript">
function handle_profile_hashbit() {
var hash_class = ""

View File

@ -1,8 +1,8 @@
{{template "header.html" . }}
{{/** TO-DO: Move this into a CSS file **/}}
{{/** TODO: Move this into a CSS file **/}}
{{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 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}}

View File

@ -1,8 +1,8 @@
{{template "header.html" . }}
{{/** TO-DO: Move this into a CSS file **/}}
{{/** TODO: Move this into a CSS file **/}}
{{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 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}}

View File

@ -13,12 +13,12 @@
<span>
<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>
{{/** 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 .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>{{end}}
</span>
</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>
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;">{{.LastUser.Name}}</a><br>
<span class="rowsmall lastReplyAt">Last: {{.LastReplyAt}}</span>

View File

@ -14,11 +14,14 @@ import (
"path/filepath"
"reflect"
"strings"
"sync"
"sync/atomic"
"text/template"
)
var defaultTheme string
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 = make(map[string]bool)
@ -65,6 +68,7 @@ type ThemeResource struct {
}
func LoadThemes() error {
changeDefaultThemeMutex.Lock()
rows, err := get_themes_stmt.Query()
if err != nil {
return err
@ -100,7 +104,7 @@ func LoadThemes() error {
if defaultThemeSwitch {
log.Print("Loading the theme '" + theme.Name + "'")
theme.Active = true
defaultTheme = uname
defaultThemeBox.Store(uname)
mapThemeTemplates(theme)
} else {
theme.Active = false
@ -113,6 +117,7 @@ func LoadThemes() error {
}
themes[uname] = theme
}
changeDefaultThemeMutex.Unlock()
return rows.Err()
}
@ -158,7 +163,7 @@ func initThemes() 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 {
if dev.DebugMode {
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.
// TO-DO: Generate the type switch instead of writing it by hand
// TO-DO: Cut the number of types in half
// TODO: Generate the type switch instead of writing it by hand
// TODO: Cut the number of types in half
func RunThemeTemplate(theme string, template string, pi interface{}, w http.ResponseWriter) {
switch tmplO := GetThemeTemplate(theme, template).(type) {
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):
tmplO(pi.(Page), w)
case string:
mapping, ok := themes[defaultTheme].TemplatesMap[template]
mapping, ok := themes[defaultThemeBox.Load().(string)].TemplatesMap[template]
if !ok {
mapping = template
}
@ -458,7 +463,7 @@ func GetThemeTemplate(theme string, template string) interface{} {
// CreateThemeTemplate creates a theme template on the current default theme
func CreateThemeTemplate(theme string, name string) {
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 {
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",
"ForkOf": "cosmo",
"MobileFriendly": true,
"HideFromThemes": true,
"Tag": "🏗️",
"URL": "github.com/Azareal/Gosora",
"Templates": [

View File

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

View File

@ -183,11 +183,11 @@ a {
/* Topic View */
/* TO-DO: How should we handle the sticky headers? */
/* TODO: How should we handle the sticky headers? */
.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 {
margin-bottom: -10px;
font-size: 19px;
@ -443,6 +443,24 @@ input, select, textarea {
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 */
.rowhead {
display: flex;
@ -536,7 +554,7 @@ input, select, textarea {
.simple .user_tag {
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 {
background-repeat: no-repeat, repeat-y;
background-size: 128px;

View File

@ -165,7 +165,7 @@ li a {
font-size: 12px;
}
.colblock_left {
/*.colblock_left {
border: 1px solid #ccc;
padding: 0px;
padding-top: 0px;
@ -181,7 +181,7 @@ li a {
overflow: hidden;
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 :( */
.colstack_left {
@ -366,6 +366,36 @@ button {
/* 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 {
background-color: rgb(255,255,234);
}
@ -373,10 +403,10 @@ button {
background-color: rgb(248,248,248);
}
.topic_sticky_head {
background-color:#FFFFEA;
background-color: #FFFFEA;
}
.topic_closed_head {
background-color:#eaeaea;
background-color: #eaeaea;
}
.topic_status {
@ -390,7 +420,9 @@ button {
color: #505050; /* 80,80,80 */
border-radius: 2px;
}
.topic_status:empty { display: none; }
.topic_status:empty {
display: none;
}
.username, .panel_tag {
text-transform: none;
@ -434,7 +466,9 @@ button.username {
color: #202020;
opacity: 0.7;
}
.post_item > .mod_button > button:hover { opacity: 0.9; }
.post_item > .mod_button > button:hover {
opacity: 0.9;
}
.user_tag {
float: right;
@ -449,8 +483,12 @@ button.username {
background-color: #ffeaff;
}
.mod_button { margin-right: 4px; }
.like_label:before, .like_count_label:before { content: "😀"; }
.mod_button {
margin-right: 4px;
}
.like_label:before, .like_count_label:before {
content: "😀";
}
.like_count_label {
color: #505050;
float: right;
@ -565,6 +603,7 @@ button.username {
background-size: 128px;
padding-left: 136px;
}
/* Profiles */
#profile_left_lane {
width: 220px;

View File

@ -18,14 +18,12 @@
.panel_compactrow .panel_tag {
font-size: 14px;
}
.panel_compactrow {
padding-left: 10px;
padding-top: 10px;
padding-bottom: 10px;
padding-right: 10px;
}
.panel_compacttext {
font-size: 14px;
}
@ -39,7 +37,9 @@
font-size: 16px;
position: static;
}
.panel_upshift:visited { color: black; }
.panel_upshift:visited {
color: black;
}
/*.panel_tag_upshift {
margin-left: 2px;
position: relative;
@ -47,31 +47,85 @@
color: #505050;
}*/
.panel_floater { float: right; }
.panel_rank_tag_admin: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: "👪"; }
.panel_floater {
float: right;
}
.panel_rank_tag_admin: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_members:before { content: "👪"; }
.forum_preset_staff:before { content: "👮"; }
.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; }
.forum_preset_announce:before {
content: "📣";
}
.forum_preset_members:before {
content: "👪";
}
.forum_preset_staff:before {
content: "👮";
}
.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_read_only:before { 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"; }
.perm_preset_no_access:before {
content: "No Access";
color: maroon;
}
.perm_preset_read_only:before {
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 {
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 "./query_gen/lib"
// TO-DO: Add the watchdog goroutine
// TODO: Add the watchdog goroutine
var topics TopicStore
type TopicStore interface {
@ -165,47 +165,47 @@ func (sts *MemoryTopicStore) GetCapacity() int {
return sts.capacity
}
type SqlTopicStore struct {
type SQLTopicStore struct {
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 = ?", "", "")
if err != nil {
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}
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)
return &topic, err
}
func (sts *SqlTopicStore) GetUnsafe(id int) (*Topic, error) {
func (sts *SQLTopicStore) GetUnsafe(id int) (*Topic, error) {
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)
topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
return &topic, err
}
func (sts *SqlTopicStore) CascadeGet(id int) (*Topic, error) {
func (sts *SQLTopicStore) CascadeGet(id int) (*Topic, error) {
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)
topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
return &topic, err
}
func (sts *SqlTopicStore) BypassGet(id int) (*Topic, error) {
func (sts *SQLTopicStore) BypassGet(id int) (*Topic, error) {
topic := &Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
return topic, err
}
func (sts *SqlTopicStore) Load(id int) error {
func (sts *SQLTopicStore) Load(id int) error {
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)
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
func (sts *SqlTopicStore) Set(item *Topic) error {
func (sts *SQLTopicStore) Set(item *Topic) error {
return nil
}
func (sts *SqlTopicStore) Add(item *Topic) error {
func (sts *SQLTopicStore) Add(item *Topic) error {
return nil
}
func (sts *SqlTopicStore) AddUnsafe(item *Topic) error {
func (sts *SQLTopicStore) AddUnsafe(item *Topic) error {
return nil
}
func (sts *SqlTopicStore) Remove(id int) error {
func (sts *SQLTopicStore) Remove(id int) error {
return nil
}
func (sts *SqlTopicStore) RemoveUnsafe(id int) error {
func (sts *SQLTopicStore) RemoveUnsafe(id int) error {
return nil
}
func (sts *SqlTopicStore) AddLastTopic(item *Topic, fid int) error {
func (sts *SQLTopicStore) AddLastTopic(item *Topic, fid int) error {
// Coming Soon...
return nil
}
func (sts *SqlTopicStore) GetCapacity() int {
func (sts *SQLTopicStore) GetCapacity() int {
return 0
}
func (sts *SqlTopicStore) GetLength() int {
func (sts *SQLTopicStore) GetLength() int {
return 0 // Return the total number of topics on the forums?
}

125
user.go
View File

@ -3,6 +3,7 @@ package main
import (
//"log"
//"fmt"
"html"
"html/template"
"net"
"net/http"
@ -15,9 +16,11 @@ import (
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 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
@ -79,7 +82,7 @@ func (user *User) Unban() error {
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?
func (user *User) ScheduleGroupUpdate(gid int, issuedBy int, duration time.Duration) error {
var temporary bool
@ -99,7 +102,7 @@ func (user *User) ScheduleGroupUpdate(gid int, issuedBy int, duration time.Durat
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 {
_, err := replace_schedule_group_stmt.Exec(user.ID, 0, 0, time.Now(), false)
if err != nil {
@ -117,18 +120,18 @@ func BcryptCheckPassword(realPassword string, password string, salt string) (err
}
// 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)
if err != nil {
return "", "", err
}
password = password + salt
hashed_password, err = BcryptGeneratePasswordNoSalt(password)
hashedPassword, err = BcryptGeneratePasswordNoSalt(password)
if err != nil {
return "", "", err
}
return hashed_password, salt, nil
return hashedPassword, salt, nil
}
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 {
var schema string = "http"
var schema = "http"
if site.EnableSsl {
schema += "s"
}
// TODO: Move these to the phrase system
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."
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
func BuildWidgets(zone string, data interface{}, headerVars *HeaderVars, r *http.Request) {
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)
if themes[defaultTheme].Sidebars == "right" {
//log.Print("themes[headerVars.ThemeName].Sidebars",themes[headerVars.ThemeName].Sidebars)
if themes[headerVars.ThemeName].Sidebars == "right" {
if len(docks.RightSidebar) != 0 {
var sbody string
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
// TODO: Do a panel specific theme?
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{
Site: site,
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 {
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")
if len(themes[defaultTheme].Resources) != 0 {
rlist := themes[defaultTheme].Resources
if len(themes[headerVars.ThemeName].Resources) != 0 {
rlist := themes[headerVars.ThemeName].Resources
for _, resource := range rlist {
if resource.Location == "global" || resource.Location == "panel" {
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 {
InternalError(err, w)
return headerVars, stats, false
}
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.WordFilters = len(wordFilterBox.Load().(WordFilterBox))
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)
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/global.js", nil)
pusher.Push("/static/jquery-3.1.1.min.js", nil)
// TO-DO: Push the theme CSS files
// TO-DO: Push the theme scripts
// TO-DO: Push avatars?
// TODO: Push the theme CSS files
// TODO: Push the theme scripts
// TODO: Push avatars?
}
return headerVars, stats, true
@ -337,19 +354,32 @@ func _simple_session_check(w http.ResponseWriter, r *http.Request, user *User) (
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) {
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{
Site: site,
Settings: settingBox.Load().(SettingBox),
ThemeName: defaultTheme, // TO-DO: Is this racey?
Themes: themes,
ThemeName: themeName,
}
if user.IsBanned {
headerVars.NoticeList = append(headerVars.NoticeList, "Your account has been suspended. Some of your permissions may have been revoked.")
}
if len(themes[defaultTheme].Resources) != 0 {
rlist := themes[defaultTheme].Resources
if len(themes[headerVars.ThemeName].Resources) != 0 {
rlist := themes[headerVars.ThemeName].Resources
for _, resource := range rlist {
if resource.Location == "global" || resource.Location == "frontend" {
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/global.js", nil)
pusher.Push("/static/jquery-3.1.1.min.js", nil)
// TO-DO: Push the theme CSS files
// TO-DO: Push the theme scripts
// TO-DO: Push avatars?
// TODO: Push the theme CSS files
// TODO: Push the theme scripts
// TODO: Push avatars?
}
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)
if halt {
return *user, false
@ -401,11 +431,14 @@ func _pre_route(w http.ResponseWriter, r *http.Request) (User, bool) {
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
}
func words_to_score(wcount int, topic bool) (score int) {
func wordsToScore(wcount int, topic bool) (score int) {
if topic {
score = 2
} else {
@ -421,11 +454,12 @@ func words_to_score(wcount int, topic bool) (score int) {
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
baseScore := 1
if topic {
_, err := increment_user_topics_stmt.Exec(1, uid)
_, err := increment_user_topics_stmt.Exec(1, user.ID)
if err != nil {
return err
}
@ -434,38 +468,40 @@ func increase_post_user_stats(wcount int, uid int, topic bool, user User) error
settings := settingBox.Load().(SettingBox)
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 {
return err
}
mod = 4
} 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 {
return err
}
mod = 1
} else {
_, err := increment_user_posts_stmt.Exec(1, uid)
_, err := increment_user_posts_stmt.Exec(1, user.ID)
if err != nil {
return err
}
}
_, err := increment_user_score_stmt.Exec(baseScore+mod, uid)
_, err := increment_user_score_stmt.Exec(baseScore+mod, user.ID)
if err != nil {
return err
}
//log.Print(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
}
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
baseScore := -1
if topic {
_, err := increment_user_topics_stmt.Exec(-1, uid)
_, err := increment_user_topics_stmt.Exec(-1, user.ID)
if err != nil {
return err
}
@ -474,28 +510,29 @@ func decrease_post_user_stats(wcount int, uid int, topic bool, user User) error
settings := settingBox.Load().(SettingBox)
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 {
return err
}
mod = 4
} 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 {
return err
}
mod = 1
} else {
_, err := increment_user_posts_stmt.Exec(-1, uid)
_, err := increment_user_posts_stmt.Exec(-1, user.ID)
if err != nil {
return err
}
}
_, err := increment_user_score_stmt.Exec(baseScore-mod, uid)
_, err := increment_user_score_stmt.Exec(baseScore-mod, user.ID)
if err != nil {
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
}

View File

@ -12,7 +12,7 @@ import (
"golang.org/x/crypto/bcrypt"
)
// TO-DO: Add the watchdog goroutine
// TODO: Add the watchdog goroutine
var users UserStore
var errAccountExists = errors.New("this username is already in use")
@ -36,46 +36,46 @@ type UserStore interface {
}
type MemoryUserStore struct {
items map[int]*User
length int
capacity int
get *sql.Stmt
register *sql.Stmt
username_exists *sql.Stmt
user_count *sql.Stmt
items map[int]*User
length int
capacity int
get *sql.Stmt
register *sql.Stmt
usernameExists *sql.Stmt
userCount *sql.Stmt
sync.RWMutex
}
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 {
log.Fatal(err)
}
// Add an admin version of register_stmt with more flexibility?
// 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 {
log.Fatal(err)
}
username_exists_stmt, err := qgen.Builder.SimpleSelect("users", "name", "name = ?", "", "")
usernameExistsStmt, err := qgen.Builder.SimpleSelect("users", "name", "name = ?", "", "")
if err != nil {
log.Fatal(err)
}
user_count_stmt, err := qgen.Builder.SimpleCount("users", "", "")
userCountStmt, err := qgen.Builder.SimpleCount("users", "", "")
if err != nil {
log.Fatal(err)
}
return &MemoryUserStore{
items: make(map[int]*User),
capacity: capacity,
get: get_stmt,
register: register_stmt,
username_exists: username_exists_stmt,
user_count: user_count_stmt,
items: make(map[int]*User),
capacity: capacity,
get: getStmt,
register: registerStmt,
usernameExists: usernameExistsStmt,
userCount: userCountStmt,
}
}
@ -135,10 +135,10 @@ func (sus *MemoryUserStore) bulkGet(ids []int) (list []*User) {
return list
}
// TO-DO: 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: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts?
// TODO: ID of 0 should always error?
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)
if idCount == 0 {
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) {
// Is this username already taken..?
err := sus.username_exists.QueryRow(username).Scan(&username)
err := sus.usernameExists.QueryRow(username).Scan(&username)
if err != ErrNoRows {
return 0, errAccountExists
}
@ -363,21 +363,21 @@ func (sus *MemoryUserStore) GetCapacity() int {
// Return the total number of users registered on the forums
func (sus *MemoryUserStore) GetGlobalCount() int {
var ucount int
err := sus.user_count.QueryRow().Scan(&ucount)
err := sus.userCount.QueryRow().Scan(&ucount)
if err != nil {
LogError(err)
}
return ucount
}
type SqlUserStore struct {
type SQLUserStore struct {
get *sql.Stmt
register *sql.Stmt
usernameExists *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 = ?", "", "")
if err != nil {
log.Fatal(err)
@ -400,7 +400,7 @@ func NewSqlUserStore() *SqlUserStore {
log.Fatal(err)
}
return &SqlUserStore{
return &SQLUserStore{
get: getStmt,
register: registerStmt,
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}
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
}
func (sus *SqlUserStore) GetUnsafe(id int) (*User, error) {
func (sus *SQLUserStore) GetUnsafe(id int) (*User, error) {
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)
@ -442,7 +442,7 @@ func (sus *SqlUserStore) GetUnsafe(id int) (*User, error) {
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}
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
}
// TO-DO: 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) {
// 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) {
var qlist string
var uidList []interface{}
for _, id := range ids {
@ -506,7 +506,7 @@ func (sus *SqlUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, err e
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}
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
}
func (sus *SqlUserStore) Load(id int) error {
func (sus *SQLUserStore) Load(id int) error {
user := &User{ID: id}
// 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)
}
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..?
err := sus.usernameExists.QueryRow(username).Scan(&username)
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
func (sus *SqlUserStore) Set(item *User) error {
func (sus *SQLUserStore) Set(item *User) error {
return nil
}
func (sus *SqlUserStore) Add(item *User) error {
func (sus *SQLUserStore) Add(item *User) error {
return nil
}
func (sus *SqlUserStore) AddUnsafe(item *User) error {
func (sus *SQLUserStore) AddUnsafe(item *User) error {
return nil
}
func (sus *SqlUserStore) Remove(id int) error {
func (sus *SQLUserStore) Remove(id int) error {
return nil
}
func (sus *SqlUserStore) RemoveUnsafe(id int) error {
func (sus *SQLUserStore) RemoveUnsafe(id int) error {
return nil
}
func (sus *SqlUserStore) GetCapacity() int {
func (sus *SQLUserStore) GetCapacity() int {
return 0
}
// Return the total number of users registered on the forums
func (sus *SqlUserStore) GetLength() int {
func (sus *SQLUserStore) GetLength() int {
var ucount int
err := sus.userCount.QueryRow().Scan(&ucount)
if err != nil {
@ -584,7 +584,7 @@ func (sus *SqlUserStore) GetLength() int {
}
return ucount
}
func (sus *SqlUserStore) GetGlobalCount() int {
func (sus *SQLUserStore) GetGlobalCount() int {
var ucount int
err := sus.userCount.QueryRow().Scan(&ucount)
if err != nil {

View File

@ -205,16 +205,16 @@ func SendEmail(email string, subject string, msg string) (res bool) {
return
}
email_data, err := con.Data()
emailData, err := con.Data()
if err != nil {
return
}
_, err = fmt.Fprintf(email_data, body)
_, err = fmt.Fprintf(emailData, body)
if err != nil {
return
}
err = email_data.Close()
err = emailData.Close()
if err != nil {
return
}
@ -229,7 +229,7 @@ func weakPassword(password string) error {
if len(password) < 8 {
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
for _, char := range password {
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 {
return errors.New("you don't have any numbers in your password")
}
@ -306,15 +306,15 @@ func wordCount(input string) (count int) {
if input == "" {
return 0
}
in_space := false
var inSpace bool
for _, value := range input {
if unicode.IsSpace(value) {
if !in_space {
in_space = true
if !inSpace {
inSpace = true
}
} else if in_space {
} else if inSpace {
count++
in_space = false
inSpace = false
}
}
return count + 1
@ -323,14 +323,14 @@ func wordCount(input string) (count int) {
func getLevel(score int) (level int) {
var base float64 = 25
var current, prev float64
exp_factor := 2.8
expFactor := 2.8
for i := 1; ; i++ {
_, bit := math.Modf(float64(i) / 10)
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
if float64(score) < current {
break
@ -344,14 +344,14 @@ func getLevelScore(getLevel int) (score int) {
var base float64 = 25
var current, prev float64
var level int
exp_factor := 2.8
expFactor := 2.8
for i := 1; ; i++ {
_, bit := math.Modf(float64(i) / 10)
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
level++
if level <= getLevel {
@ -364,16 +364,16 @@ func getLevelScore(getLevel int) (score int) {
func getLevels(maxLevel int) []float64 {
var base float64 = 25
var current, prev float64 // = 0
exp_factor := 2.8
expFactor := 2.8
var out []float64
out = append(out, 0)
for i := 1; i <= maxLevel; i++ {
_, bit := math.Modf(float64(i) / 10)
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
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) {
_, err = add_modlog_entry_stmt.Exec(action, elementID, elementType, ipaddress, actorID)
if err != nil {
return err
}
return nil
return err
}
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)
if err != nil {
return err
}
return nil
return err
}

View File

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