diff --git a/README.md b/README.md
index 3d5d78f3..3ebd9582 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
# Gosora [![Azareal's Discord Chat](https://img.shields.io/badge/style-Invite-7289DA.svg?style=flat&label=Discord)](https://discord.gg/eyYvtTf)
-A super fast forum software written in Go.
+A super fast forum software written in Go. You can talk to us on our Discord chat!
-The initial code-base was forked from one of my side projects, but has now gone far beyond that. We're still fairly early in development, so the code-base might change at an incredible rate. We plan to start stabilising it somewhat once we enter alpha.
+The initial code-base was forked from one of my side projects, but has now gone far beyond that. We're still fairly early in development, so the code-base might change at an incredible rate. We plan to stop making as many breaking changes once we release the first alpha.
If you like this software, please give it a star and give us some feedback :)
diff --git a/database.go b/database.go
index 4c52352f..3a6804ca 100644
--- a/database.go
+++ b/database.go
@@ -25,6 +25,16 @@ func initDatabase() (err error) {
return err
}
+ // We have to put this here, otherwise LoadForums() won't be able to get the last poster data when building it's forums
+ log.Print("Initialising the user and topic stores")
+ if config.CacheTopicUser == CACHE_STATIC {
+ users = NewMemoryUserStore(config.UserCacheCapacity)
+ topics = NewMemoryTopicStore(config.TopicCacheCapacity)
+ } else {
+ users = NewSQLUserStore()
+ topics = NewSQLTopicStore()
+ }
+
log.Print("Loading the forums.")
fstore = NewMemoryForumStore()
err = fstore.LoadForums()
@@ -45,7 +55,7 @@ func initDatabase() (err error) {
}
log.Print("Loading the plugins.")
- err = LoadPlugins()
+ err = initExtend()
if err != nil {
return err
}
diff --git a/experimental/module_ottojs.go b/experimental/module_ottojs.go
deleted file mode 100644
index 126ff1c1..00000000
--- a/experimental/module_ottojs.go
+++ /dev/null
@@ -1,47 +0,0 @@
-/* Copyright Azareal 2016 - 2017 */
-package main
-import "github.com/robertkrimen/otto"
-
-var vm *Otto
-var js_plugins map[string]*otto.Script = make(map[string]*otto.Script)
-var js_vars map[string]*otto.Object = make(map[string]*otto.Object)
-
-func init()
-{
- var err error
- vm = otto.New()
- js_vars["current_page"], err = vm.Object(`current_page = {}`)
- if err != nil {
- log.Fatal(err)
- }
-}
-
-func js_add_plugin(plugin string) error
-{
- script, err := otto.Compile("./extend/" + plugin + ".js")
- if err != nil {
- return err
- }
- vm.Run(script)
- return nil
-}
-
-func js_add_hook(hook string, plugin string)
-{
- hooks[hook] = func(data interface{}) interface{} {
- switch d := data.(type) {
- case Page:
- current_page := js_vars["current_page"]
- current_page.Set("Title", d.Title)
- case TopicPage:
-
- case ProfilePage:
-
- case Reply:
-
- default:
- log.Print("Not a valid JS datatype")
- }
- }
-}
-
diff --git a/extend.go b/extend.go
index eaf66f80..f91980a6 100644
--- a/extend.go
+++ b/extend.go
@@ -6,8 +6,10 @@
*/
package main
-import "log"
-import "net/http"
+import (
+ "log"
+ "net/http"
+)
var plugins = make(map[string]*Plugin)
@@ -15,7 +17,6 @@ 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, // TODO: Rename this hook to topic_rrow_assign
}
// Hooks with a variable number of arguments
@@ -26,6 +27,7 @@ var vhooks = map[string]func(...interface{}) interface{}{
"forum_trow_assign": nil,
"topics_topic_row_assign": nil,
//"topics_user_row_assign": nil,
+ "topic_reply_row_assign": nil,
"create_group_preappend": nil, // What is this? Investigate!
"topic_create_pre_loop": nil,
}
@@ -100,6 +102,15 @@ type Plugin struct {
Uninstall func() error
Hooks map[string]int
+ Data interface{} // Usually used for hosting the VMs / reusable elements of non-native plugins
+}
+
+func initExtend() (err error) {
+ err = InitPluginLangs()
+ if err != nil {
+ return err
+ }
+ return LoadPlugins()
}
// LoadPlugins polls the database to see which plugins have been activated and which have been installed
@@ -111,8 +122,7 @@ func LoadPlugins() error {
defer rows.Close()
var uname string
- var active bool
- var installed bool
+ var active, installed bool
for rows.Next() {
err = rows.Scan(&uname, &active, &installed)
if err != nil {
diff --git a/extend/heytherejs/main.js b/extend/heytherejs/main.js
new file mode 100644
index 00000000..86f23665
--- /dev/null
+++ b/extend/heytherejs/main.js
@@ -0,0 +1,5 @@
+current_page.test = true;
+
+// This shouldn't ever fail
+var errmsg = "gotcha";
+errmsg;
\ No newline at end of file
diff --git a/extend/heytherejs/plugin.json b/extend/heytherejs/plugin.json
new file mode 100644
index 00000000..5862681c
--- /dev/null
+++ b/extend/heytherejs/plugin.json
@@ -0,0 +1,7 @@
+{
+ "UName":"heytherejs",
+ "Name":"HeythereJS",
+ "Author":"Azareal",
+ "URL":"https://github.com/Azareal/Gosora",
+ "Main":"main.js"
+}
\ No newline at end of file
diff --git a/forum.go b/forum.go
index a1fdf735..bb2c2728 100644
--- a/forum.go
+++ b/forum.go
@@ -19,23 +19,26 @@ type ForumAdmin struct {
}
type Forum struct {
- ID int
- Link string
- Name string
- Desc string
- Active bool
- Preset string
- ParentID int
- ParentType string
- TopicCount int
- LastTopicLink string
- LastTopic string
+ ID int
+ Link string
+ Name string
+ Desc string
+ Active bool
+ Preset string
+ ParentID int
+ ParentType string
+ TopicCount int
+
+ LastTopic *Topic
LastTopicID int
- LastReplyer string
+ LastReplyer *User
LastReplyerID int
- LastTopicTime string
+ LastTopicTime string // So that we can re-calculate the relative time on the spot in /forums/
+
+ //LastLock sync.RWMutex // ? - Is this safe to copy? Use a pointer to it? Should we do an fstore.Reload() instead?
}
+// ? - What is this for?
type ForumSimple struct {
ID int
Name string
@@ -43,6 +46,36 @@ type ForumSimple struct {
Preset string
}
+func (forum *Forum) Copy() (fcopy Forum) {
+ //forum.LastLock.RLock()
+ fcopy = *forum
+ //forum.LastLock.RUnlock()
+ return fcopy
+}
+
+/*func (forum *Forum) GetLast() (topic *Topic, user *User) {
+ forum.LastLock.RLock()
+ topic = forum.LastTopic
+ if topic == nil {
+ topic = &Topic{ID: 0}
+ }
+
+ user = forum.LastReplyer
+ if user == nil {
+ user = &User{ID: 0}
+ }
+ forum.LastLock.RUnlock()
+ return topic, user
+}
+
+func (forum *Forum) SetLast(topic *Topic, user *User) {
+ forum.LastLock.Lock()
+ forum.LastTopic = topic
+ forum.LastReplyer = user
+ forum.LastLock.Unlock()
+}*/
+
+// TODO: Write tests for this
func (forum *Forum) Update(name string, desc string, active bool, preset string) error {
if name == "" {
name = forum.Name
@@ -53,9 +86,13 @@ func (forum *Forum) Update(name string, desc string, active bool, preset string)
return err
}
if forum.Preset != preset || preset == "custom" || preset == "" {
- permmapToQuery(presetToPermmap(preset), forum.ID)
+ err = permmapToQuery(presetToPermmap(preset), forum.ID)
+ if err != nil {
+ return err
+ }
}
_ = fstore.Reload(forum.ID)
+ return nil
}
// TODO: Replace this sorting mechanism with something a lot more efficient
@@ -72,6 +109,11 @@ func (sf SortForum) Less(i, j int) bool {
return sf[i].ID < sf[j].ID
}
+// ! Don't use this outside of tests and possibly template_init.go
+func makeDummyForum(fid int, link string, name string, desc string, active bool, preset string, parentID int, parentType string, topicCount int) *Forum {
+ return &Forum{ID: fid, Link: link, Name: name, Desc: desc, Active: active, Preset: preset, ParentID: parentID, ParentType: parentType, TopicCount: topicCount}
+}
+
func buildForumURL(slug string, fid int) string {
if slug == "" {
return "/forum/" + strconv.Itoa(fid)
diff --git a/forum_store.go b/forum_store.go
index 73abbf68..97751978 100644
--- a/forum_store.go
+++ b/forum_store.go
@@ -27,14 +27,13 @@ type ForumStore interface {
LoadForums() error
DirtyGet(id int) *Forum
Get(id int) (*Forum, error)
- GetCopy(id int) (Forum, error)
BypassGet(id int) (*Forum, error)
Reload(id int) error // ? - Should we move this to ForumCache? It might require us to do some unnecessary casting though
//Update(Forum) error
Delete(id int) error
- IncrementTopicCount(id int) error
- DecrementTopicCount(id int) error
- UpdateLastTopic(topicName string, tid int, username string, uid int, time string, fid int) error
+ AddTopic(tid int, uid int, fid int) error
+ RemoveTopic(fid int) error
+ UpdateLastTopic(tid int, uid int, fid int) error
Exists(id int) bool
GetAll() ([]*Forum, error)
GetAllIDs() ([]int, error)
@@ -51,6 +50,7 @@ type ForumCache interface {
CacheGet(id int) (*Forum, error)
CacheSet(forum *Forum) error
CacheDelete(id int)
+ GetLength() int
}
// MemoryForumStore is a struct which holds an arbitrary number of forums in memory, usually all of them, although we might introduce functionality to hold a smaller subset in memory for sites with an extremely large number of forums
@@ -58,7 +58,6 @@ type MemoryForumStore struct {
forums sync.Map // map[int]*Forum
forumView atomic.Value // []*Forum
//fids []int
- forumCount int
get *sql.Stmt
getAll *sql.Stmt
@@ -94,10 +93,6 @@ func NewMemoryForumStore() *MemoryForumStore {
// TODO: Add support for subforums
func (mfs *MemoryForumStore) LoadForums() error {
- log.Print("Adding the uncategorised forum")
- forumUpdateMutex.Lock()
- defer forumUpdateMutex.Unlock()
-
var forumView []*Forum
addForum := func(forum *Forum) {
mfs.forums.Store(forum.ID, forum)
@@ -114,8 +109,8 @@ func (mfs *MemoryForumStore) LoadForums() error {
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)
+ 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.LastTopicID, &forum.LastReplyerID)
if err != nil {
return err
}
@@ -129,15 +124,27 @@ func (mfs *MemoryForumStore) LoadForums() error {
}
forum.Link = buildForumURL(nameToSlug(forum.Name), forum.ID)
- forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID)
- addForum(&forum)
+
+ topic, err := topics.Get(forum.LastTopicID)
+ if err != nil {
+ topic = getDummyTopic()
+ }
+ user, err := users.Get(forum.LastReplyerID)
+ if err != nil {
+ user = getDummyUser()
+ }
+ forum.LastTopic = topic
+ forum.LastReplyer = user
+ //forum.SetLast(topic, user)
+
+ addForum(forum)
}
- mfs.forumCount = i
mfs.forumView.Store(forumView)
return rows.Err()
}
// TODO: Hide social groups too
+// ? - Will this be hit a lot by plugin_socialgroups?
func (mfs *MemoryForumStore) rebuildView() {
var forumView []*Forum
mfs.forums.Range(func(_ interface{}, value interface{}) bool {
@@ -173,46 +180,75 @@ func (mfs *MemoryForumStore) Get(id int) (*Forum, error) {
if !ok || fint.(*Forum).Name == "" {
var forum = &Forum{ID: id}
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 forum, err
+ }
forum.Link = buildForumURL(nameToSlug(forum.Name), forum.ID)
- forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID)
+
+ topic, err := topics.Get(forum.LastTopicID)
+ if err != nil {
+ topic = getDummyTopic()
+ }
+ user, err := users.Get(forum.LastReplyerID)
+ if err != nil {
+ user = getDummyUser()
+ }
+ forum.LastTopic = topic
+ forum.LastReplyer = user
+ //forum.SetLast(topic, user)
+
+ mfs.CacheSet(forum)
return forum, err
}
return fint.(*Forum), nil
}
-func (mfs *MemoryForumStore) GetCopy(id int) (Forum, error) {
- fint, ok := mfs.forums.Load(id)
- if !ok || fint.(*Forum).Name == "" {
- var forum = Forum{ID: id}
- 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 *fint.(*Forum), nil
-}
-
func (mfs *MemoryForumStore) BypassGet(id int) (*Forum, error) {
- var forum = Forum{ID: id}
+ var forum = &Forum{ID: id}
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 nil, err
+ }
forum.Link = buildForumURL(nameToSlug(forum.Name), forum.ID)
- forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID)
- return &forum, err
+
+ topic, err := topics.Get(forum.LastTopicID)
+ if err != nil {
+ topic = getDummyTopic()
+ }
+ user, err := users.Get(forum.LastReplyerID)
+ if err != nil {
+ user = getDummyUser()
+ }
+ forum.LastTopic = topic
+ forum.LastReplyer = user
+ //forum.SetLast(topic, user)
+
+ return forum, err
}
func (mfs *MemoryForumStore) Reload(id int) error {
- var forum = Forum{ID: id}
+ var forum = &Forum{ID: id}
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
}
forum.Link = buildForumURL(nameToSlug(forum.Name), forum.ID)
- forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID)
- mfs.CacheSet(&forum)
+ topic, err := topics.Get(forum.LastTopicID)
+ if err != nil {
+ topic = getDummyTopic()
+ }
+ user, err := users.Get(forum.LastReplyerID)
+ if err != nil {
+ user = getDummyUser()
+ }
+ forum.LastTopic = topic
+ forum.LastReplyer = user
+ //forum.SetLast(topic, user)
+
+ mfs.CacheSet(forum)
return nil
}
@@ -281,8 +317,6 @@ func (mfs *MemoryForumStore) Delete(id int) error {
if id == 1 {
return errors.New("You cannot delete the Reports forum")
}
- forumUpdateMutex.Lock()
- defer forumUpdateMutex.Unlock()
_, err := mfs.delete.Exec(id)
if err != nil {
return err
@@ -291,53 +325,40 @@ func (mfs *MemoryForumStore) Delete(id int) error {
return nil
}
-// ! Is this racey?
-func (mfs *MemoryForumStore) IncrementTopicCount(id int) error {
- forum, err := mfs.Get(id)
+func (mfs *MemoryForumStore) AddTopic(tid int, uid int, fid int) error {
+ _, err := updateForumCacheStmt.Exec(tid, uid, fid)
if err != nil {
return err
}
- _, err = addTopicsToForumStmt.Exec(1, id)
+ _, err = addTopicsToForumStmt.Exec(1, fid)
if err != nil {
return err
}
- forum.TopicCount++
+ // TODO: Bypass the database and update this with a lock or an unsafe atomic swap
+ mfs.Reload(fid)
return nil
}
-// ! Is this racey?
-func (mfs *MemoryForumStore) DecrementTopicCount(id int) error {
- forum, err := mfs.Get(id)
+// TODO: Update the forum cache with the latest topic
+func (mfs *MemoryForumStore) RemoveTopic(fid int) error {
+ _, err := removeTopicsFromForumStmt.Exec(1, fid)
if err != nil {
return err
}
- _, err = removeTopicsFromForumStmt.Exec(1, id)
- if err != nil {
- return err
- }
- forum.TopicCount--
+ // TODO: Bypass the database and update this with a lock or an unsafe atomic swap
+ mfs.Reload(fid)
return nil
}
+// DEPRECATED. forum.Update() will be the way to do this in the future, once it's completed
// TODO: Have a pointer to the last topic rather than storing it on the forum itself
-// ! Is this racey?
-func (mfs *MemoryForumStore) UpdateLastTopic(topicName string, tid int, username string, uid int, time string, fid int) error {
- forum, err := mfs.Get(fid)
+func (mfs *MemoryForumStore) UpdateLastTopic(tid int, uid int, fid int) error {
+ _, err := updateForumCacheStmt.Exec(tid, uid, fid)
if err != nil {
return err
}
-
- _, err = updateForumCacheStmt.Exec(topicName, tid, username, uid, fid)
- if err != nil {
- return err
- }
-
- forum.LastTopic = topicName
- forum.LastTopicID = tid
- forum.LastReplyer = username
- forum.LastReplyerID = uid
- forum.LastTopicTime = time
-
+ // TODO: Bypass the database and update this with a lock or an unsafe atomic swap
+ mfs.Reload(fid)
return nil
}
@@ -354,19 +375,25 @@ func (mfs *MemoryForumStore) Create(forumName string, forumDesc string, active b
}
fid := int(fid64)
- mfs.forums.Store(fid, &Forum{fid, buildForumURL(nameToSlug(forumName), fid), forumName, forumDesc, active, preset, 0, "", 0, "", "", 0, "", 0, ""})
- mfs.forumCount++
+ err = mfs.Reload(fid)
+ if err != nil {
+ return 0, err
+ }
- // TODO: Add a GroupStore. How would it interact with the ForumStore?
permmapToQuery(presetToPermmap(preset), fid)
forumCreateMutex.Unlock()
-
- if active {
- mfs.rebuildView()
- }
return fid, nil
}
+// ! Might be slightly inaccurate, if the sync.Map is constantly shifting and churning, but it'll stabilise eventually. Also, slow. Don't use this on every request x.x
+func (mfs *MemoryForumStore) GetLength() (length int) {
+ mfs.forums.Range(func(_ interface{}, value interface{}) bool {
+ length++
+ return true
+ })
+ return length
+}
+
// 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 (mfs *MemoryForumStore) GetGlobalCount() (fcount int) {
diff --git a/gen_mysql.go b/gen_mysql.go
index 7dfd8c2c..90afab99 100644
--- a/gen_mysql.go
+++ b/gen_mysql.go
@@ -186,7 +186,7 @@ func _gen_mysql() (err error) {
}
log.Print("Preparing getForums statement.")
- getForumsStmt, err = db.Prepare("SELECT `fid`,`name`,`desc`,`active`,`preset`,`parentID`,`parentType`,`topicCount`,`lastTopic`,`lastTopicID`,`lastReplyer`,`lastReplyerID`,`lastTopicTime` FROM `forums` ORDER BY fid ASC")
+ getForumsStmt, err = db.Prepare("SELECT `fid`,`name`,`desc`,`active`,`preset`,`parentID`,`parentType`,`topicCount`,`lastTopicID`,`lastReplyerID` FROM `forums` ORDER BY fid ASC")
if err != nil {
return err
}
@@ -534,7 +534,7 @@ func _gen_mysql() (err error) {
}
log.Print("Preparing updateForumCache statement.")
- updateForumCacheStmt, err = db.Prepare("UPDATE `forums` SET `lastTopic` = ?,`lastTopicID` = ?,`lastReplyer` = ?,`lastReplyerID` = ?,`lastTopicTime` = UTC_TIMESTAMP() WHERE `fid` = ?")
+ updateForumCacheStmt, err = db.Prepare("UPDATE `forums` SET `lastTopicID` = ?,`lastReplyerID` = ? WHERE `fid` = ?")
if err != nil {
return err
}
diff --git a/gen_pgsql.go b/gen_pgsql.go
index cfa68b2d..eaae16c0 100644
--- a/gen_pgsql.go
+++ b/gen_pgsql.go
@@ -80,7 +80,7 @@ func _gen_pgsql() (err error) {
}
log.Print("Preparing updateForumCache statement.")
- updateForumCacheStmt, err = db.Prepare("UPDATE `forums` SET `lastTopic` = ?,`lastTopicID` = ?,`lastReplyer` = ?,`lastReplyerID` = ?,`lastTopicTime` = LOCALTIMESTAMP() WHERE `fid` = ?")
+ updateForumCacheStmt, err = db.Prepare("UPDATE `forums` SET `lastTopicID` = ?,`lastReplyerID` = ? WHERE `fid` = ?")
if err != nil {
return err
}
diff --git a/general_test.go b/general_test.go
index b34053c6..8aa39f7c 100644
--- a/general_test.go
+++ b/general_test.go
@@ -53,14 +53,6 @@ func gloinit() error {
log.Fatal(err)
}
- if config.CacheTopicUser == CACHE_STATIC {
- users = NewMemoryUserStore(config.UserCacheCapacity)
- topics = NewMemoryTopicStore(config.TopicCacheCapacity)
- } else {
- users = NewSQLUserStore()
- topics = NewSQLTopicStore()
- }
-
log.Print("Loading the static files.")
err = initStaticFiles()
if err != nil {
@@ -548,7 +540,7 @@ func BenchmarkQueriesSerial(b *testing.B) {
}
})
- var replyItem Reply
+ var replyItem ReplyUser
var isSuperAdmin bool
var group int
b.Run("topic_replies_scan", func(b *testing.B) {
diff --git a/group.go b/group.go
index 799c4bbd..54c40686 100644
--- a/group.go
+++ b/group.go
@@ -11,6 +11,7 @@ type GroupAdmin struct {
CanDelete bool
}
+// ! Fix the data races
type Group struct {
ID int
Name string
@@ -25,3 +26,7 @@ type Group struct {
Forums []ForumPerms
CanSee []int // The IDs of the forums this group can see
}
+
+func (group *Group) Copy() Group {
+ return *group
+}
diff --git a/main.go b/main.go
index 8d439100..2079420c 100644
--- a/main.go
+++ b/main.go
@@ -82,14 +82,6 @@ func main() {
log.Fatal(err)
}
- if config.CacheTopicUser == CACHE_STATIC {
- users = NewMemoryUserStore(config.UserCacheCapacity)
- topics = NewMemoryTopicStore(config.TopicCacheCapacity)
- } else {
- users = NewSQLUserStore()
- topics = NewSQLTopicStore()
- }
-
log.Print("Loading the static files.")
err = initStaticFiles()
if err != nil {
diff --git a/member_routes.go b/member_routes.go
index 977a71a5..3d8b5f8a 100644
--- a/member_routes.go
+++ b/member_routes.go
@@ -10,7 +10,6 @@ import (
"regexp"
"strconv"
"strings"
- "time"
)
// ? - Should we add a new permission or permission zone (like per-forum permissions) specifically for profile comment creation
@@ -79,7 +78,7 @@ func routeTopicCreate(w http.ResponseWriter, r *http.Request, user User, sfid st
// Do a bulk forum fetch, just in case it's the SqlForumStore?
forum := fstore.DirtyGet(ffid)
if forum.Name != "" && forum.Active {
- fcopy := *forum
+ fcopy := forum.Copy()
if hooks["topic_create_frow_assign"] != nil {
// TODO: Add the skip feature to all the other row based hooks?
if runHook("topic_create_frow_assign", &fcopy).(bool) {
@@ -144,12 +143,6 @@ func routeTopicCreateSubmit(w http.ResponseWriter, r *http.Request, user User) {
return
}
- err = fstore.IncrementTopicCount(fid)
- if err != nil {
- InternalError(err, w)
- return
- }
-
_, err = addSubscriptionStmt.Exec(user.ID, lastID, "topic")
if err != nil {
InternalError(err, w)
@@ -163,7 +156,7 @@ func routeTopicCreateSubmit(w http.ResponseWriter, r *http.Request, user User) {
return
}
- err = fstore.UpdateLastTopic(topicName, int(lastID), user.Name, user.ID, time.Now().Format("2006-01-02 15:04:05"), fid)
+ err = fstore.AddTopic(int(lastID), user.ID, fid)
if err != nil && err != ErrNoRows {
InternalError(err, w)
}
@@ -219,7 +212,14 @@ func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) {
InternalError(err, w)
return
}
- err = fstore.UpdateLastTopic(topic.Title, tid, user.Name, user.ID, time.Now().Format("2006-01-02 15:04:05"), topic.ParentID)
+
+ // Flush the topic out of the cache
+ tcache, ok := topics.(TopicCache)
+ if ok {
+ tcache.CacheRemove(tid)
+ }
+
+ err = fstore.UpdateLastTopic(tid, user.ID, topic.ParentID)
if err != nil && err != ErrNoRows {
InternalError(err, w)
return
@@ -247,12 +247,6 @@ func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) {
go notifyWatchers(lastID)
}
- // Flush the topic out of the cache
- tcache, ok := topics.(TopicCache)
- if ok {
- tcache.CacheRemove(tid)
- }
-
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
err = user.increasePostStats(wcount, false)
if err != nil {
@@ -629,7 +623,7 @@ func routeReportSubmit(w http.ResponseWriter, r *http.Request, user User, sitemI
InternalError(err, w)
return
}
- err = fstore.UpdateLastTopic(title, int(lastID), user.Name, user.ID, time.Now().Format("2006-01-02 15:04:05"), fid)
+ err = fstore.UpdateLastTopic(int(lastID), user.ID, fid)
if err != nil && err != ErrNoRows {
InternalError(err, w)
return
diff --git a/misc_test.go b/misc_test.go
index 66f191dc..8e70a6d5 100644
--- a/misc_test.go
+++ b/misc_test.go
@@ -269,19 +269,12 @@ func TestForumStore(t *testing.T) {
}
forum, err = fstore.Get(0)
- if err == ErrNoRows {
- t.Error("Couldn't find FID #0")
- } else if err != nil {
+ if err == nil {
+ t.Error("FID #0 shouldn't exist")
+ } else if err != ErrNoRows {
t.Fatal(err)
}
- if forum.ID != 0 {
- t.Error("forum.ID doesn't not match the requested UID. Got '" + strconv.Itoa(forum.ID) + "' instead.")
- }
- if forum.Name != "Uncategorised" {
- t.Error("FID #0 is named '" + forum.Name + "' and not 'Uncategorised'")
- }
-
forum, err = fstore.Get(1)
if err == ErrNoRows {
t.Error("Couldn't find FID #1")
@@ -311,8 +304,8 @@ func TestForumStore(t *testing.T) {
}
ok = fstore.Exists(0)
- if !ok {
- t.Error("FID #0 should exist")
+ if ok {
+ t.Error("FID #0 shouldn't exist")
}
ok = fstore.Exists(1)
diff --git a/mod_routes.go b/mod_routes.go
index 97f5e34d..06ca5d5e 100644
--- a/mod_routes.go
+++ b/mod_routes.go
@@ -4,6 +4,7 @@ import (
//"log"
//"fmt"
"html"
+ "log"
"net"
"net/http"
"strconv"
@@ -26,7 +27,7 @@ func routeEditTopic(w http.ResponseWriter, r *http.Request, user User) {
return
}
- oldTopic, err := topics.Get(tid)
+ topic, err := topics.Get(tid)
if err == ErrNoRows {
PreErrorJSQ("The topic you tried to edit doesn't exist.", w, r, isJs)
return
@@ -36,7 +37,7 @@ func routeEditTopic(w http.ResponseWriter, r *http.Request, user User) {
}
// TODO: Add hooks to make use of headerLite
- _, ok := SimpleForumUserCheck(w, r, &user, oldTopic.ParentID)
+ _, ok := SimpleForumUserCheck(w, r, &user, topic.ParentID)
if !ok {
return
}
@@ -47,25 +48,20 @@ func routeEditTopic(w http.ResponseWriter, r *http.Request, user User) {
topicName := r.PostFormValue("topic_name")
topicContent := html.EscapeString(r.PostFormValue("topic_content"))
+ log.Print("topicContent ", topicContent)
- // TODO: Move this bit to the TopicStore
- _, err = editTopicStmt.Exec(topicName, preparseMessage(topicContent), parseMessage(html.EscapeString(preparseMessage(topicContent))), tid)
+ err = topic.Update(topicName, topicContent)
if err != nil {
InternalErrorJSQ(err, w, r, isJs)
return
}
- err = fstore.UpdateLastTopic(topicName, tid, user.Name, user.ID, time.Now().Format("2006-01-02 15:04:05"), oldTopic.ParentID)
+ err = fstore.UpdateLastTopic(topic.ID, user.ID, topic.ParentID)
if err != nil && err != ErrNoRows {
- InternalError(err, w)
+ InternalErrorJSQ(err, w, r, isJs)
return
}
- tcache, ok := topics.(TopicCache)
- if ok {
- tcache.CacheRemove(oldTopic.ID)
- }
-
if !isJs {
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
} else {
diff --git a/module_ottojs.go b/module_ottojs.go
new file mode 100644
index 00000000..80e69fef
--- /dev/null
+++ b/module_ottojs.go
@@ -0,0 +1,91 @@
+/*
+*
+* OttoJS Plugin Module
+* Copyright Azareal 2016 - 2018
+*
+ */
+package main
+
+import (
+ "errors"
+
+ "github.com/robertkrimen/otto"
+)
+
+type OttoPluginLang struct {
+ vm *otto.Otto
+ plugins map[string]*otto.Script
+ vars map[string]*otto.Object
+}
+
+func init() {
+ pluginLangs["ottojs"] = &OttoPluginLang{
+ plugins: make(map[string]*otto.Script),
+ vars: make(map[string]*otto.Object),
+ }
+}
+
+func (js *OttoPluginLang) Init() (err error) {
+ js.vm = otto.New()
+ js.vars["current_page"], err = js.vm.Object(`var current_page = {}`)
+ return err
+}
+
+func (js *OttoPluginLang) GetName() string {
+ return "ottojs"
+}
+
+func (js *OttoPluginLang) GetExts() []string {
+ return []string{".js"}
+}
+
+func (js *OttoPluginLang) AddPlugin(meta PluginMeta) (plugin *Plugin, err error) {
+ script, err := js.vm.Compile("./extend/"+meta.UName+"/"+meta.Main, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var pluginInit = func() error {
+ retValue, err := js.vm.Run(script)
+ if err != nil {
+ return err
+ }
+ if retValue.IsString() {
+ ret, err := retValue.ToString()
+ if err != nil {
+ return err
+ }
+ if ret != "" {
+ return errors.New(ret)
+ }
+ }
+ return nil
+ }
+ var pluginActivate func() error
+ var pluginDeactivate func()
+ var pluginInstall func() error
+ var pluginUninstall func() error
+
+ plugin = NewPlugin(meta.UName, meta.Name, meta.Author, meta.URL, meta.Settings, meta.Tag, "ottojs", pluginInit, pluginActivate, pluginDeactivate, pluginInstall, pluginUninstall)
+
+ plugin.Data = script
+ return plugin, nil
+}
+
+/*func (js *OttoPluginLang) addHook(hook string, plugin string) {
+ hooks[hook] = func(data interface{}) interface{} {
+ switch d := data.(type) {
+ case Page:
+ currentPage := js.vars["current_page"]
+ currentPage.Set("Title", d.Title)
+ case TopicPage:
+
+ case ProfilePage:
+
+ case Reply:
+
+ default:
+ log.Print("Not a valid JS datatype")
+ }
+ }
+}*/
diff --git a/mysql.sql b/mysql.sql
index 6aea704e..c0c77e1f 100644
--- a/mysql.sql
+++ b/mysql.sql
@@ -26,11 +26,8 @@ CREATE TABLE `forums`(
`preset` varchar(100) DEFAULT '' not null,
`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,
- `lastReplyer` varchar(100) DEFAULT '' not null,
`lastReplyerID` int DEFAULT 0 not null,
- `lastTopicTime` datetime not null,
primary key(`fid`)
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
@@ -233,7 +230,7 @@ INSERT INTO users_groups(`name`,`permissions`,`plugin_perms`) VALUES ('Awaiting
INSERT INTO users_groups(`name`,`permissions`,`plugin_perms`,`tag`) VALUES ('Not Loggedin','{"ViewTopic":true}','{}','Guest');
INSERT INTO forums(`name`,`active`) VALUES ('Reports',0);
-INSERT INTO forums(`name`,`lastTopicTime`,`lastTopicID`,`lastReplyer`,`lastReplyerID`,`lastTopic`) VALUES ('General',UTC_TIMESTAMP(),1,"Admin",1,'Test Topic');
+INSERT INTO forums(`name`,`lastTopicID`,`lastReplyerID`) VALUES ("General",1,1);
INSERT INTO forums_permissions(`gid`,`fid`,`permissions`) VALUES (1,1,'{"ViewTopic":true,"CreateReply":true,"CreateTopic":true,"PinTopic":true,"CloseTopic":true}');
INSERT INTO forums_permissions(`gid`,`fid`,`permissions`) VALUES (2,1,'{"ViewTopic":true,"CreateReply":true,"CloseTopic":true}');
diff --git a/pages.go b/pages.go
index 3f54ff31..feb273d7 100644
--- a/pages.go
+++ b/pages.go
@@ -54,7 +54,7 @@ type TopicPage struct {
Title string
CurrentUser User
Header *HeaderVars
- ItemList []Reply
+ ItemList []ReplyUser
Topic TopicUser
Page int
LastPage int
@@ -72,7 +72,7 @@ type ForumPage struct {
CurrentUser User
Header *HeaderVars
ItemList []*TopicsRow
- Forum Forum
+ Forum *Forum
Page int
LastPage int
}
@@ -88,7 +88,7 @@ type ProfilePage struct {
Title string
CurrentUser User
Header *HeaderVars
- ItemList []Reply
+ ItemList []ReplyUser
ProfileOwner User
}
diff --git a/plugin_helloworld.go b/plugin_helloworld.go
deleted file mode 100644
index 60f8d6f4..00000000
--- a/plugin_helloworld.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package main
-
-func init() {
- plugins["helloworld"] = NewPlugin("helloworld", "Hello World", "Azareal", "http://github.com/Azareal", "", "", "", initHelloworld, nil, deactivateHelloworld, nil, nil)
-}
-
-// init_helloworld is separate from init() as we don't want the plugin to run if the plugin is disabled
-func initHelloworld() error {
- plugins["helloworld"].AddHook("rrow_assign", helloworldReply)
- return nil
-}
-
-func deactivateHelloworld() {
- plugins["helloworld"].RemoveHook("rrow_assign", helloworldReply)
-}
-
-func helloworldReply(data interface{}) interface{} {
- reply := data.(*Reply)
- reply.Content = "Hello World!"
- reply.ContentHtml = "Hello World!"
- reply.Tag = "Auto"
- return nil
-}
diff --git a/plugin_heythere.go b/plugin_heythere.go
new file mode 100644
index 00000000..1e16aaaf
--- /dev/null
+++ b/plugin_heythere.go
@@ -0,0 +1,24 @@
+package main
+
+func init() {
+ plugins["heythere"] = NewPlugin("heythere", "Hey There", "Azareal", "http://github.com/Azareal", "", "", "", initHeythere, nil, deactivateHeythere, nil, nil)
+}
+
+// init_heythere is separate from init() as we don't want the plugin to run if the plugin is disabled
+func initHeythere() error {
+ plugins["heythere"].AddHook("topic_reply_row_assign", heythereReply)
+ return nil
+}
+
+func deactivateHeythere() {
+ plugins["heythere"].RemoveHook("topic_reply_row_assign", heythereReply)
+}
+
+func heythereReply(data ...interface{}) interface{} {
+ currentUser := data[0].(*TopicPage).CurrentUser
+ reply := data[1].(*ReplyUser)
+ reply.Content = "Hey there, " + currentUser.Name + "!"
+ reply.ContentHtml = "Hey there, " + currentUser.Name + "!"
+ reply.Tag = "Auto"
+ return nil
+}
diff --git a/plugin_socialgroups.go b/plugin_socialgroups.go
index f0b488f7..4f8651bd 100644
--- a/plugin_socialgroups.go
+++ b/plugin_socialgroups.go
@@ -56,7 +56,7 @@ type SocialGroupPage struct {
CurrentUser User
Header *HeaderVars
ItemList []*TopicsRow
- Forum Forum
+ Forum *Forum
SocialGroup *SocialGroup
Page int
LastPage int
diff --git a/pluginlangs.go b/pluginlangs.go
new file mode 100644
index 00000000..ac61c2fd
--- /dev/null
+++ b/pluginlangs.go
@@ -0,0 +1,116 @@
+package main
+
+import (
+ "encoding/json"
+ "errors"
+ "io/ioutil"
+ "path/filepath"
+)
+
+var pluginLangs = make(map[string]PluginLang)
+
+// For non-native plugins to bind JSON files to. E.g. JS and Lua
+type PluginMeta struct {
+ UName string
+ Name string
+ Author string
+ URL string
+ Settings string
+ Tag string
+
+ Main string // The main file
+ Hooks map[string]string // Hooks mapped to functions
+}
+
+type PluginLang interface {
+ GetName() string
+ GetExts() []string
+
+ Init() error
+ AddPlugin(meta PluginMeta) (*Plugin, error)
+ //AddHook(name string, handler interface{}) error
+ //RemoveHook(name string, handler interface{})
+ //RunHook(name string, data interface{}) interface{}
+ //RunVHook(name string data ...interface{}) interface{}
+}
+
+/*
+var ext = filepath.Ext(pluginFile.Name())
+if ext == ".txt" || ext == ".go" {
+ continue
+}
+*/
+
+func InitPluginLangs() error {
+ for _, pluginLang := range pluginLangs {
+ pluginLang.Init()
+ }
+
+ pluginList, err := GetPluginFiles()
+ if err != nil {
+ return err
+ }
+
+ for _, pluginItem := range pluginList {
+ pluginFile, err := ioutil.ReadFile("./extend/" + pluginItem + "/plugin.json")
+ if err != nil {
+ return err
+ }
+
+ var plugin PluginMeta
+ err = json.Unmarshal(pluginFile, &plugin)
+ if err != nil {
+ return err
+ }
+
+ if plugin.UName == "" {
+ return errors.New("The UName field must not be blank on plugin '" + pluginItem + "'")
+ }
+ if plugin.Name == "" {
+ return errors.New("The Name field must not be blank on plugin '" + pluginItem + "'")
+ }
+ if plugin.Author == "" {
+ return errors.New("The Author field must not be blank on plugin '" + pluginItem + "'")
+ }
+ if plugin.Main == "" {
+ return errors.New("Couldn't find a main file for plugin '" + pluginItem + "'")
+ }
+
+ var ext = filepath.Ext(plugin.Main)
+ pluginLang, err := ExtToPluginLang(ext)
+ if err != nil {
+ return err
+ }
+ pplugin, err := pluginLang.AddPlugin(plugin)
+ if err != nil {
+ return err
+ }
+ plugins[plugin.UName] = pplugin
+ }
+ return nil
+}
+
+func GetPluginFiles() (pluginList []string, err error) {
+ pluginFiles, err := ioutil.ReadDir("./extend")
+ if err != nil {
+ return nil, err
+ }
+ for _, pluginFile := range pluginFiles {
+ if !pluginFile.IsDir() {
+ continue
+ }
+ pluginList = append(pluginList, pluginFile.Name())
+ }
+ return pluginList, nil
+}
+
+func ExtToPluginLang(ext string) (PluginLang, error) {
+ for _, pluginLang := range pluginLangs {
+ for _, registeredExt := range pluginLang.GetExts() {
+ if registeredExt == ext {
+ return pluginLang, nil
+ }
+ }
+ }
+ return nil, errors.New("No plugin lang handlers are capable of handling extension '" + ext + "'")
+}
diff --git a/query_gen/main.go b/query_gen/main.go
index 08db342c..f13cefd3 100644
--- a/query_gen/main.go
+++ b/query_gen/main.go
@@ -215,7 +215,7 @@ func write_selects(adapter qgen.DB_Adapter) error {
adapter.SimpleSelect("getGroups", "users_groups", "gid, name, permissions, plugin_perms, is_mod, is_admin, is_banned, tag", "", "", "")
- adapter.SimpleSelect("getForums", "forums", "fid, name, desc, active, preset, parentID, parentType, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime", "", "fid ASC", "")
+ adapter.SimpleSelect("getForums", "forums", "fid, name, desc, active, preset, parentID, parentType, topicCount, lastTopicID, lastReplyerID", "", "fid ASC", "")
adapter.SimpleSelect("getForumsPermissions", "forums_permissions", "gid, fid, permissions", "", "gid ASC, fid ASC", "")
@@ -358,7 +358,7 @@ func write_updates(adapter qgen.DB_Adapter) error {
adapter.SimpleUpdate("removeTopicsFromForum", "forums", "topicCount = topicCount - ?", "fid = ?")
- adapter.SimpleUpdate("updateForumCache", "forums", "lastTopic = ?, lastTopicID = ?, lastReplyer = ?, lastReplyerID = ?, lastTopicTime = UTC_TIMESTAMP()", "fid = ?")
+ adapter.SimpleUpdate("updateForumCache", "forums", "lastTopicID = ?, lastReplyerID = ?", "fid = ?")
adapter.SimpleUpdate("addLikesToTopic", "topics", "likeCount = likeCount + ?", "tid = ?")
diff --git a/reply.go b/reply.go
index fcaedf30..3ac90fa5 100644
--- a/reply.go
+++ b/reply.go
@@ -8,8 +8,7 @@ package main
// ? - Should we add a reply store to centralise all the reply logic? Would this cover profile replies too or would that be seperate?
-type Reply struct /* Should probably rename this to ReplyUser and rename ReplyShort to Reply */
-{
+type ReplyUser struct {
ID int
ParentID int
Content string
@@ -36,7 +35,7 @@ type Reply struct /* Should probably rename this to ReplyUser and rename ReplySh
ActionIcon string
}
-type ReplyShort struct {
+type Reply struct {
ID int
ParentID int
Content string
@@ -51,14 +50,18 @@ type ReplyShort struct {
LikeCount int
}
-func getReply(id int) (*ReplyShort, error) {
- reply := ReplyShort{ID: id}
+func (reply *Reply) Copy() Reply {
+ return *reply
+}
+
+func getReply(id int) (*Reply, error) {
+ reply := Reply{ID: id}
err := getReplyStmt.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IPAddress, &reply.LikeCount)
return &reply, err
}
-func getUserReply(id int) (*ReplyShort, error) {
- reply := ReplyShort{ID: id}
+func getUserReply(id int) (*Reply, error) {
+ reply := Reply{ID: id}
err := getUserReplyStmt.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IPAddress)
return &reply, err
}
diff --git a/routes.go b/routes.go
index 29a992f0..dafc9574 100644
--- a/routes.go
+++ b/routes.go
@@ -159,6 +159,7 @@ func routeTopics(w http.ResponseWriter, r *http.Request, user User) {
return
}
+ // TODO: Make CanSee a method on *Group with a canSee field?
var canSee []int
if user.IsSuperAdmin {
canSee, err = fstore.GetAllVisibleIDs()
@@ -379,7 +380,7 @@ func routeForum(w http.ResponseWriter, r *http.Request, user User, sfid string)
topicItem.LastUser = userList[topicItem.LastReplyBy]
}
- pi := ForumPage{forum.Name, user, headerVars, topicList, *forum, page, lastPage}
+ pi := ForumPage{forum.Name, user, headerVars, topicList, forum, page, lastPage}
if preRenderHooks["pre_render_view_forum"] != nil {
if runPreRenderHook("pre_render_view_forum", w, r, &user, &pi) {
return
@@ -417,16 +418,22 @@ func routeForums(w http.ResponseWriter, r *http.Request, user User) {
}
for _, fid := range canSee {
- //log.Print(forums[fid])
- var forum = *fstore.DirtyGet(fid)
+ // Avoid data races by copying the struct into something we can freely mold without worrying about breaking something somewhere else
+ var forum = fstore.DirtyGet(fid).Copy()
if forum.ParentID == 0 && forum.Name != "" && forum.Active {
if forum.LastTopicID != 0 {
- forum.LastTopicTime, err = relativeTime(forum.LastTopicTime)
- if err != nil {
- InternalError(err, w)
+ //topic, user := forum.GetLast()
+ //if topic.ID != 0 && user.ID != 0 {
+ if forum.LastTopic.ID != 0 && forum.LastReplyer.ID != 0 {
+ forum.LastTopicTime, err = relativeTime(forum.LastTopic.LastReplyAt)
+ if err != nil {
+ InternalError(err, w)
+ return
+ }
+ } else {
+ forum.LastTopicTime = ""
}
} else {
- forum.LastTopic = "None"
forum.LastTopicTime = ""
}
if hooks["forums_frow_assign"] != nil {
@@ -448,7 +455,7 @@ func routeForums(w http.ResponseWriter, r *http.Request, user User) {
func routeTopicID(w http.ResponseWriter, r *http.Request, user User) {
var err error
var page, offset int
- var replyList []Reply
+ var replyList []ReplyUser
page, _ = strconv.Atoi(r.FormValue("page"))
@@ -465,7 +472,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) {
}
// Get the topic...
- topic, err := getTopicuser(tid)
+ topic, err := getTopicUser(tid)
if err == ErrNoRows {
NotFound(w, r)
return
@@ -488,7 +495,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) {
BuildWidgets("view_topic", &topic, headerVars, r)
- topic.Content = parseMessage(topic.Content)
+ topic.ContentHTML = parseMessage(topic.Content)
topic.ContentLines = strings.Count(topic.Content, "\n")
// We don't want users posting in locked topics...
@@ -543,6 +550,8 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) {
page = 1
}
+ tpage := TopicPage{topic.Title, user, headerVars, replyList, topic, page, lastPage}
+
// Get the replies..
rows, err := getTopicRepliesOffsetStmt.Query(topic.ID, offset, config.ItemsPerPage)
if err == ErrNoRows {
@@ -554,7 +563,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) {
}
defer rows.Close()
- replyItem := Reply{ClassName: ""}
+ replyItem := ReplyUser{ClassName: ""}
for rows.Next() {
err := rows.Scan(&replyItem.ID, &replyItem.Content, &replyItem.CreatedBy, &replyItem.CreatedAt, &replyItem.LastEdit, &replyItem.LastEditBy, &replyItem.Avatar, &replyItem.CreatedByName, &replyItem.Group, &replyItem.URLPrefix, &replyItem.URLName, &replyItem.Level, &replyItem.IPAddress, &replyItem.LikeCount, &replyItem.ActionType)
if err != nil {
@@ -628,9 +637,8 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) {
}
replyItem.Liked = false
- // TODO: Rename this to topic_rrow_assign
- if hooks["rrow_assign"] != nil {
- runHook("rrow_assign", &replyItem)
+ if vhooks["topic_reply_row_assign"] != nil {
+ runVhook("topic_reply_row_assign", &tpage, &replyItem)
}
replyList = append(replyList, replyItem)
}
@@ -640,7 +648,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) {
return
}
- tpage := TopicPage{topic.Title, user, headerVars, replyList, topic, page, lastPage}
+ tpage.ItemList = replyList
if preRenderHooks["pre_render_view_topic"] != nil {
if runPreRenderHook("pre_render_view_topic", w, r, &user, &tpage) {
return
@@ -658,7 +666,7 @@ func routeProfile(w http.ResponseWriter, r *http.Request, user User) {
var err error
var replyContent, replyCreatedByName, replyCreatedAt, replyAvatar, replyTag, replyClassName string
var rid, replyCreatedBy, replyLastEdit, replyLastEditBy, replyLines, replyGroup int
- var replyList []Reply
+ var replyList []ReplyUser
// SEO URLs...
halves := strings.Split(r.URL.Path[len("/user/"):], ".")
@@ -736,7 +744,7 @@ func routeProfile(w http.ResponseWriter, r *http.Request, user User) {
// TODO: Add a hook here
- replyList = append(replyList, Reply{rid, puser.ID, replyContent, parseMessage(replyContent), replyCreatedBy, buildProfileURL(nameToSlug(replyCreatedByName), replyCreatedBy), replyCreatedByName, replyGroup, replyCreatedAt, replyLastEdit, replyLastEditBy, replyAvatar, replyClassName, replyLines, replyTag, "", "", "", 0, "", replyLiked, replyLikeCount, "", ""})
+ replyList = append(replyList, ReplyUser{rid, puser.ID, replyContent, parseMessage(replyContent), replyCreatedBy, buildProfileURL(nameToSlug(replyCreatedByName), replyCreatedBy), replyCreatedByName, replyGroup, replyCreatedAt, replyLastEdit, replyLastEditBy, replyAvatar, replyClassName, replyLines, replyTag, "", "", "", 0, "", replyLiked, replyLikeCount, "", ""})
}
err = rows.Err()
if err != nil {
diff --git a/template_forums.go b/template_forums.go
index de35f365..00501d12 100644
--- a/template_forums.go
+++ b/template_forums.go
@@ -73,7 +73,7 @@ w.Write(forums_0)
if len(tmpl_forums_vars.ItemList) != 0 {
for _, item := range tmpl_forums_vars.ItemList {
w.Write(forums_1)
-if item.Desc != "" || item.LastTopicTime != "" {
+if item.Desc != "" || item.LastTopic.Title != "" {
w.Write(forums_2)
}
w.Write(forums_3)
@@ -93,21 +93,25 @@ w.Write([]byte(item.Name))
w.Write(forums_10)
}
w.Write(forums_11)
-w.Write([]byte(item.LastTopicLink))
+w.Write([]byte(item.LastTopic.Link))
w.Write(forums_12)
-w.Write([]byte(item.LastTopic))
+if item.LastTopic.Title != "" {
+w.Write([]byte(item.LastTopic.Title))
+} else {
w.Write(forums_13)
-if item.LastTopicTime != "" {
-w.Write(forums_14)
-w.Write([]byte(item.LastTopicTime))
-w.Write(forums_15)
}
+w.Write(forums_14)
+if item.LastTopicTime != "" {
+w.Write(forums_15)
+w.Write([]byte(item.LastTopicTime))
w.Write(forums_16)
}
-} else {
w.Write(forums_17)
}
+} else {
w.Write(forums_18)
+}
+w.Write(forums_19)
w.Write(footer_0)
if len(tmpl_forums_vars.Header.Themes) != 0 {
for _, item := range tmpl_forums_vars.Header.Themes {
diff --git a/template_init.go b/template_init.go
index b705580d..eae8398b 100644
--- a/template_init.go
+++ b/template_init.go
@@ -106,9 +106,9 @@ func compileTemplates() error {
log.Print("Compiling the templates")
- topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, "Date", "Date", 0, "", "127.0.0.1", 0, 1, "classname", "weird-data", buildProfileURL("fake-user", 62), "Fake User", config.DefaultGroup, "", 0, "", "", "", "", 58, false}
- 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, "", ""})
+ topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, "Date", "Date", 0, "", "127.0.0.1", 0, 1, "classname", "weird-data", buildProfileURL("fake-user", 62), "Fake User", config.DefaultGroup, "", 0, "", "", "", "", "", 58, false}
+ var replyList []ReplyUser
+ replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", config.DefaultGroup, "", 0, 0, "", "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, "", ""})
var varList = make(map[string]VarItem)
tpage := TopicPage{"Title", user, headerVars, replyList, topic, 1, 1}
@@ -135,6 +135,7 @@ func compileTemplates() error {
}
for _, forum := range forums {
+ //log.Printf("*forum %+v\n", *forum)
forumList = append(forumList, *forum)
}
varList = make(map[string]VarItem)
@@ -154,7 +155,7 @@ func compileTemplates() error {
//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})
- forumItem := Forum{1, "general", "General Forum", "Where the general stuff happens", true, "all", 0, "", 0, "", "", 0, "", 0, ""}
+ forumItem := makeDummyForum(1, "general-forum.1", "General Forum", "Where the general stuff happens", true, "all", 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 {
diff --git a/template_list.go b/template_list.go
index 4c52b488..a0aaf3fa 100644
--- a/template_list.go
+++ b/template_list.go
@@ -185,50 +185,52 @@ var topic_63 = []byte(`), url(/static/post-avatar-bg.jpg);background-position: 0
var topic_64 = []byte(`-1`)
var topic_65 = []byte(`0px;background-repeat:no-repeat, repeat-y;`)
var topic_66 = []byte(`">
+ `)
+var topic_67 = []byte(`
`)
-var topic_67 = []byte(`
+var topic_68 = []byte(`
`)
-var topic_69 = []byte(`
+var topic_69 = []byte(`" class="username real_username">`)
+var topic_70 = []byte(`
`)
-var topic_70 = []byte(` `)
-var topic_74 = []byte(` `)
-var topic_76 = []byte(` `)
-var topic_78 = []byte(` `)
-var topic_80 = []byte(`
+var topic_71 = []byte(` `)
+var topic_75 = []byte(` `)
+var topic_77 = []byte(` `)
+var topic_79 = []byte(` `)
+var topic_81 = []byte(`
+var topic_82 = []byte(`?session=`)
+var topic_83 = []byte(`&type=reply" class="mod_button report_item" title="Flag Reply">
`)
-var topic_83 = []byte(``)
-var topic_84 = []byte(` `)
-var topic_85 = []byte(``)
-var topic_86 = []byte(` `)
-var topic_87 = []byte(``)
-var topic_88 = []byte(` `)
-var topic_89 = []byte(`
+var topic_84 = []byte(``)
+var topic_85 = []byte(` `)
+var topic_86 = []byte(``)
+var topic_87 = []byte(` `)
+var topic_88 = []byte(``)
+var topic_89 = []byte(` `)
+var topic_90 = []byte(`
`)
-var topic_90 = []byte(`
+var topic_91 = []byte(`
`)
-var topic_91 = []byte(`
+var topic_92 = []byte(`
`)
-var topic_93 = []byte(`
+var topic_94 = []byte(`
@@ -632,17 +634,18 @@ var forums_11 = []byte(`
`)
-var forums_13 = []byte(`
+var forums_13 = []byte(`None`)
+var forums_14 = []byte(`
`)
-var forums_14 = []byte(``)
-var forums_15 = []byte(` `)
-var forums_16 = []byte(`
+var forums_15 = []byte(``)
+var forums_16 = []byte(` `)
+var forums_17 = []byte(`
`)
-var forums_17 = []byte(`You don't have access to any forums.
`)
-var forums_18 = []byte(`
+var forums_18 = []byte(`You don't have access to any forums.
`)
+var forums_19 = []byte(`
@@ -653,7 +656,7 @@ var topics_0 = []byte(`
-
+
`)
var topics_1 = []byte(`
- {{range .ItemList}}
+ {{range .ItemList}}
{{if .Desc}}
{{.Name}}
{{.Desc}}
@@ -15,7 +15,7 @@
{{end}}
- {{.LastTopic}}
+ {{if .LastTopic.Title}}{{.LastTopic.Title}}{{else}}None{{end}}
{{if .LastTopicTime}}{{.LastTopicTime}} {{end}}
diff --git a/templates/topic.html b/templates/topic.html
index 3e90c3c1..a8992cff 100644
--- a/templates/topic.html
+++ b/templates/topic.html
@@ -23,7 +23,7 @@
-
{{.Topic.Content}}
+
{{.Topic.ContentHTML}}
{{.Topic.Content}}
@@ -56,6 +56,7 @@
{{else}}
+ {{/** TODO: We might end up with s in the inline editor, fix this **/}}
{{.ContentHtml}}
diff --git a/templates/topic_alt.html b/templates/topic_alt.html
index 3a88c875..e1a37e38 100644
--- a/templates/topic_alt.html
+++ b/templates/topic_alt.html
@@ -27,7 +27,7 @@
{{if .Topic.Tag}}{{else}}{{end}}
-
{{.Topic.Content}}
+
{{.Topic.ContentHTML}}
{{.Topic.Content}}