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:
parent
ba36814d8d
commit
91f70d2a4a
|
@ -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
|
||||
|
|
1
auth.go
1
auth.go
|
@ -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)
|
||||
|
|
2
cache.go
2
cache.go
|
@ -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.")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
2
files.go
2
files.go
|
@ -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()
|
||||
}
|
||||
|
|
2
forum.go
2
forum.go
|
@ -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)
|
||||
}
|
||||
|
|
311
forum_store.go
311
forum_store.go
|
@ -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
|
||||
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,22 +65,36 @@ 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{
|
||||
return &MemoryForumStore{
|
||||
get: getStmt,
|
||||
getAll: getAllStmt,
|
||||
forumCount: forumCountStmt,
|
||||
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 {
|
||||
|
@ -81,7 +102,7 @@ func (sfs *StaticForumStore) LoadForums() error {
|
|||
}
|
||||
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
|
||||
|
|
21
gen_mysql.go
21
gen_mysql.go
|
@ -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 {
|
||||
|
|
14
gen_pgsql.go
14
gen_pgsql.go
|
@ -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 {
|
||||
|
@ -287,5 +281,11 @@ func _gen_pgsql() (err error) {
|
|||
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
|
||||
}
|
||||
|
|
|
@ -68,6 +68,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
48
main.go
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
12
misc_test.go
12
misc_test.go
|
@ -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) {
|
||||
|
|
|
@ -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,12 +164,18 @@ func route_delete_topic(w http.ResponseWriter, r *http.Request, user User) {
|
|||
//log.Print("Topic #" + strconv.Itoa(tid) + " was deleted by User #" + strconv.Itoa(user.ID))
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
|
||||
topicCreator, err := users.CascadeGet(topic.CreatedBy)
|
||||
if err == nil {
|
||||
wcount := wordCount(topic.Content)
|
||||
err = decrease_post_user_stats(wcount, topic.CreatedBy, true, user)
|
||||
err = topicCreator.decreasePostStats(wcount, true)
|
||||
if err != nil {
|
||||
InternalError(err, w)
|
||||
return
|
||||
}
|
||||
} else if err != ErrNoRows {
|
||||
InternalError(err, w)
|
||||
return
|
||||
}
|
||||
|
||||
err = fstore.DecrementTopicCount(topic.ParentID)
|
||||
if err != nil && err != ErrNoRows {
|
||||
|
@ -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,12 +411,18 @@ func route_reply_delete_submit(w http.ResponseWriter, r *http.Request, user User
|
|||
w.Write(successJSONBytes)
|
||||
}
|
||||
|
||||
replyCreator, err := users.CascadeGet(reply.CreatedBy)
|
||||
if err == nil {
|
||||
wcount := wordCount(reply.Content)
|
||||
err = decrease_post_user_stats(wcount, reply.CreatedBy, false, user)
|
||||
err = replyCreator.decreasePostStats(wcount, false)
|
||||
if err != nil {
|
||||
InternalErrorJSQ(err, w, r, isJs)
|
||||
return
|
||||
}
|
||||
} else if err != ErrNoRows {
|
||||
InternalErrorJSQ(err, w, r, isJs)
|
||||
return
|
||||
}
|
||||
_, err = remove_replies_from_topic_stmt.Exec(1, reply.ParentID)
|
||||
if err != nil {
|
||||
InternalErrorJSQ(err, w, r, isJs)
|
||||
|
@ -524,7 +542,7 @@ func route_ips(w http.ResponseWriter, r *http.Request, user User) {
|
|||
|
||||
ip := html.EscapeString(r.URL.Path[len("/users/ips/"):])
|
||||
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 {
|
||||
|
|
2
mysql.go
2
mysql.go
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
13
pages.go
13
pages.go
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
6
pgsql.go
6
pgsql.go
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 += `
|
||||
|
|
|
@ -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 += `
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -15,6 +15,7 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
// nolint
|
||||
func write_statements(adapter qgen.DB_Adapter) error {
|
||||
err := create_tables(adapter)
|
||||
if err != nil {
|
||||
|
@ -84,6 +85,7 @@ func write_statements(adapter qgen.DB_Adapter) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// nolint
|
||||
func create_tables(adapter qgen.DB_Adapter) error {
|
||||
qgen.Install.CreateTable("users", "utf8mb4", "utf8mb4_general_ci",
|
||||
[]qgen.DB_Table_Column{
|
||||
|
@ -120,10 +122,10 @@ func create_tables(adapter qgen.DB_Adapter) error {
|
|||
|
||||
// What should we do about global penalties? Put them on the users table for speed? Or keep them here?
|
||||
// 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,""},
|
||||
|
@ -175,13 +177,22 @@ func create_tables(adapter qgen.DB_Adapter) error {
|
|||
},
|
||||
)
|
||||
|
||||
qgen.Install.CreateTable("sync", "", "",
|
||||
[]qgen.DB_Table_Column{
|
||||
qgen.DB_Table_Column{"last_update", "datetime", 0, false, false, ""},
|
||||
},
|
||||
[]qgen.DB_Table_Key{},
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 = ?", "", "")
|
||||
|
@ -194,7 +205,6 @@ func write_selects(adapter qgen.DB_Adapter) error {
|
|||
|
||||
adapter.SimpleSelect("get_password", "users", "password,salt", "uid = ?", "", "")
|
||||
|
||||
|
||||
adapter.SimpleSelect("get_settings", "settings", "name, content, type", "", "", "")
|
||||
|
||||
adapter.SimpleSelect("get_setting", "settings", "content, type", "name = ?", "", "")
|
||||
|
@ -259,9 +269,12 @@ func write_selects(adapter qgen.DB_Adapter) error {
|
|||
|
||||
adapter.SimpleSelect("get_expired_scheduled_groups", "users_groups_scheduler", "uid", "UTC_TIMESTAMP() > revert_at AND temporary = 1", "", "")
|
||||
|
||||
adapter.SimpleSelect("get_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 = ?", "", "?,?")
|
||||
|
||||
|
@ -280,12 +293,14 @@ func write_left_joins(adapter qgen.DB_Adapter) error {
|
|||
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 = ?", "", "")
|
||||
|
||||
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(),?,?,?,?")
|
||||
|
||||
|
@ -315,7 +330,6 @@ func write_inserts(adapter qgen.DB_Adapter) error {
|
|||
|
||||
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()")
|
||||
|
@ -327,6 +341,7 @@ func write_inserts(adapter qgen.DB_Adapter) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// nolint
|
||||
func write_replaces(adapter qgen.DB_Adapter) error {
|
||||
adapter.SimpleReplace("add_forum_perms_to_group", "forums_permissions", "gid,fid,preset,permissions", "?,?,?,?")
|
||||
|
||||
|
@ -335,6 +350,7 @@ func write_replaces(adapter qgen.DB_Adapter) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// nolint
|
||||
func write_updates(adapter qgen.DB_Adapter) error {
|
||||
adapter.SimpleUpdate("add_replies_to_topic", "topics", "postCount = postCount + ?, lastReplyBy = ?, lastReplyAt = UTC_TIMESTAMP()", "tid = ?")
|
||||
|
||||
|
@ -386,9 +402,6 @@ func write_updates(adapter qgen.DB_Adapter) error {
|
|||
|
||||
adapter.SimpleUpdate("edit_profile_reply", "users_replies", "content = ?, parsed_content = ?", "rid = ?")
|
||||
|
||||
//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 = ?")
|
||||
|
@ -415,15 +428,17 @@ func write_updates(adapter qgen.DB_Adapter) error {
|
|||
|
||||
adapter.SimpleUpdate("update_word_filter", "word_filters", "find = ?, replacement = ?", "wfid = ?")
|
||||
|
||||
adapter.SimpleUpdate("bump_sync", "sync", "last_update = UTC_TIMESTAMP()", "")
|
||||
|
||||
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 = ?")
|
||||
|
@ -436,6 +451,7 @@ func write_deletes(adapter qgen.DB_Adapter) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// nolint
|
||||
func write_simple_counts(adapter qgen.DB_Adapter) error {
|
||||
adapter.SimpleCount("report_exists", "topics", "data = ? AND data != '' AND parentID = 1", "")
|
||||
|
||||
|
@ -446,6 +462,7 @@ func write_simple_counts(adapter qgen.DB_Adapter) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// 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", ""},
|
||||
|
@ -465,10 +482,12 @@ func write_insert_selects(adapter qgen.DB_Adapter) error {
|
|||
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", ""},
|
||||
|
|
11
router.go
11
router.go
|
@ -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,9 +42,9 @@ func (router *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
var /*extra_data, */prefix string
|
||||
var /*extraData, */ prefix string
|
||||
if req.URL.Path[len(req.URL.Path)-1] != '/' {
|
||||
//extra_data = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:]
|
||||
//extraData = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:]
|
||||
prefix = req.URL.Path[:strings.LastIndexByte(req.URL.Path, '/')+1]
|
||||
} else {
|
||||
prefix = req.URL.Path
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
package main
|
||||
|
||||
import "log"
|
||||
|
||||
//import "strings"
|
||||
import "os"
|
||||
|
||||
|
@ -15,7 +16,7 @@ func main() {
|
|||
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
|
||||
|
@ -145,6 +146,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
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)
|
||||
|
|
|
@ -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"}},
|
||||
|
|
168
routes.go
168
routes.go
|
@ -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,26 +746,24 @@ 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?
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
ctpage := CreateTopicPage{"Create Topic", user, headerVars, forumList, fid}
|
||||
if preRenderHooks["pre_render_create_topic"] != nil {
|
||||
|
@ -789,7 +789,7 @@ func route_topic_create_submit(w http.ResponseWriter, r *http.Request, user User
|
|||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,3 @@
|
|||
CREATE TABLE `sync` (
|
||||
`last_update` datetime not null
|
||||
);
|
|
@ -0,0 +1,3 @@
|
|||
CREATE TABLE `sync` (
|
||||
`last_update` timestamp not null
|
||||
);
|
|
@ -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" {
|
||||
|
|
16
site.go
16
site.go
|
@ -2,13 +2,12 @@ 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
|
||||
{
|
||||
type Site struct {
|
||||
Name string
|
||||
Email string
|
||||
Url string
|
||||
|
@ -18,8 +17,7 @@ type Site struct
|
|||
HasProxy bool
|
||||
}
|
||||
|
||||
type DB_Config struct
|
||||
{
|
||||
type DB_Config struct {
|
||||
Host string
|
||||
Username string
|
||||
Password string
|
||||
|
@ -27,8 +25,7 @@ type DB_Config struct
|
|||
Port string
|
||||
}
|
||||
|
||||
type Config struct
|
||||
{
|
||||
type Config struct {
|
||||
SslPrivkey string
|
||||
SslFullchain string
|
||||
|
||||
|
@ -54,8 +51,7 @@ type Config struct
|
|||
ItemsPerPage int
|
||||
}
|
||||
|
||||
type DevConfig struct
|
||||
{
|
||||
type DevConfig struct {
|
||||
DebugMode bool
|
||||
SuperDebug bool
|
||||
Profiling bool
|
||||
|
|
40
tasks.go
40
tasks.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
107
template_init.go
107
template_init.go
|
@ -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)
|
||||
}
|
||||
}
|
||||
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...
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
45
templates.go
45
templates.go
|
@ -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] == '#' {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"> | 🔒︎</span>{{end}}
|
||||
{{if .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | 📍︎</span>{{end}}
|
||||
</span>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 = ""
|
||||
|
|
|
@ -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}}"><</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}}">></a></div>{{end}}
|
||||
|
|
|
@ -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}}"><</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}}">></a></div>{{end}}
|
||||
|
|
|
@ -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"> | 🔒︎</span>{{end}}
|
||||
{{if .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | 📍︎</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>
|
||||
|
|
27
themes.go
27
themes.go
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"FullImage": "cosmo-conflux.png",
|
||||
"ForkOf": "cosmo",
|
||||
"MobileFriendly": true,
|
||||
"HideFromThemes": true,
|
||||
"Tag": "🏗️",
|
||||
"URL": "github.com/Azareal/Gosora",
|
||||
"Templates": [
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"Creator": "Azareal",
|
||||
"FullImage": "cosmo.png",
|
||||
"MobileFriendly": true,
|
||||
"HideFromThemes": true,
|
||||
"Tag": "🏗️",
|
||||
"URL": "github.com/Azareal/Gosora",
|
||||
"Templates": [
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
125
user.go
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
@ -41,30 +41,30 @@ type MemoryUserStore struct {
|
|||
capacity int
|
||||
get *sql.Stmt
|
||||
register *sql.Stmt
|
||||
username_exists *sql.Stmt
|
||||
user_count *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)
|
||||
}
|
||||
|
@ -72,10 +72,10 @@ func NewMemoryUserStore(capacity int) *MemoryUserStore {
|
|||
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,
|
||||
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 {
|
||||
|
|
44
utils.go
44
utils.go
|
@ -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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue