Began work on support for JS Plugins.
Renamed the rrow_assign hook to topic_reply_row_assign and gave it access to more data. Fixed a bug where the topic store wouldn't fetch the last reply time for a topic. Refactored the process of adding and removing topics from and to a *Forum. Fixed a bug where editing the opening post of a topic would yield a vast number of <br>s instead of blank lines. Selecting text in Shadow now has it's own CSS instead of falling back onto the browser defaults. Fixed a bug in Shadow where not all the headers filled up the space they should. Fixed a bug in Shadow where the footer is broken on mobile. Added an ARIA Label to the topic list. Refactored the last poster logic to reduce the number of bugs. Renamed ReplyShort to Reply and Reply to ReplyUser. Added a Copy method to Reply, Group, Forum, User, and Topic. Rewrote Hello World into something slightly more useful for new plugin devs to learn off. Added the GetLength() method to ForumCache.
This commit is contained in:
parent
f5d5f755bb
commit
47963e10a9
|
@ -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)
|
# 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 :)
|
If you like this software, please give it a star and give us some feedback :)
|
||||||
|
|
||||||
|
|
12
database.go
12
database.go
|
@ -25,6 +25,16 @@ func initDatabase() (err error) {
|
||||||
return err
|
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.")
|
log.Print("Loading the forums.")
|
||||||
fstore = NewMemoryForumStore()
|
fstore = NewMemoryForumStore()
|
||||||
err = fstore.LoadForums()
|
err = fstore.LoadForums()
|
||||||
|
@ -45,7 +55,7 @@ func initDatabase() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Print("Loading the plugins.")
|
log.Print("Loading the plugins.")
|
||||||
err = LoadPlugins()
|
err = initExtend()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
20
extend.go
20
extend.go
|
@ -6,8 +6,10 @@
|
||||||
*/
|
*/
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "log"
|
import (
|
||||||
import "net/http"
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
var plugins = make(map[string]*Plugin)
|
var plugins = make(map[string]*Plugin)
|
||||||
|
|
||||||
|
@ -15,7 +17,6 @@ var plugins = make(map[string]*Plugin)
|
||||||
var hooks = map[string][]func(interface{}) interface{}{
|
var hooks = map[string][]func(interface{}) interface{}{
|
||||||
"forums_frow_assign": nil,
|
"forums_frow_assign": nil,
|
||||||
"topic_create_frow_assign": nil,
|
"topic_create_frow_assign": nil,
|
||||||
"rrow_assign": nil, // TODO: Rename this hook to topic_rrow_assign
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hooks with a variable number of arguments
|
// Hooks with a variable number of arguments
|
||||||
|
@ -26,6 +27,7 @@ var vhooks = map[string]func(...interface{}) interface{}{
|
||||||
"forum_trow_assign": nil,
|
"forum_trow_assign": nil,
|
||||||
"topics_topic_row_assign": nil,
|
"topics_topic_row_assign": nil,
|
||||||
//"topics_user_row_assign": nil,
|
//"topics_user_row_assign": nil,
|
||||||
|
"topic_reply_row_assign": nil,
|
||||||
"create_group_preappend": nil, // What is this? Investigate!
|
"create_group_preappend": nil, // What is this? Investigate!
|
||||||
"topic_create_pre_loop": nil,
|
"topic_create_pre_loop": nil,
|
||||||
}
|
}
|
||||||
|
@ -100,6 +102,15 @@ type Plugin struct {
|
||||||
Uninstall func() error
|
Uninstall func() error
|
||||||
|
|
||||||
Hooks map[string]int
|
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
|
// 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()
|
defer rows.Close()
|
||||||
|
|
||||||
var uname string
|
var uname string
|
||||||
var active bool
|
var active, installed bool
|
||||||
var installed bool
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
err = rows.Scan(&uname, &active, &installed)
|
err = rows.Scan(&uname, &active, &installed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
current_page.test = true;
|
||||||
|
|
||||||
|
// This shouldn't ever fail
|
||||||
|
var errmsg = "gotcha";
|
||||||
|
errmsg;
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"UName":"heytherejs",
|
||||||
|
"Name":"HeythereJS",
|
||||||
|
"Author":"Azareal",
|
||||||
|
"URL":"https://github.com/Azareal/Gosora",
|
||||||
|
"Main":"main.js"
|
||||||
|
}
|
70
forum.go
70
forum.go
|
@ -19,23 +19,26 @@ type ForumAdmin struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Forum struct {
|
type Forum struct {
|
||||||
ID int
|
ID int
|
||||||
Link string
|
Link string
|
||||||
Name string
|
Name string
|
||||||
Desc string
|
Desc string
|
||||||
Active bool
|
Active bool
|
||||||
Preset string
|
Preset string
|
||||||
ParentID int
|
ParentID int
|
||||||
ParentType string
|
ParentType string
|
||||||
TopicCount int
|
TopicCount int
|
||||||
LastTopicLink string
|
|
||||||
LastTopic string
|
LastTopic *Topic
|
||||||
LastTopicID int
|
LastTopicID int
|
||||||
LastReplyer string
|
LastReplyer *User
|
||||||
LastReplyerID int
|
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 {
|
type ForumSimple struct {
|
||||||
ID int
|
ID int
|
||||||
Name string
|
Name string
|
||||||
|
@ -43,6 +46,36 @@ type ForumSimple struct {
|
||||||
Preset string
|
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 {
|
func (forum *Forum) Update(name string, desc string, active bool, preset string) error {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
name = forum.Name
|
name = forum.Name
|
||||||
|
@ -53,9 +86,13 @@ func (forum *Forum) Update(name string, desc string, active bool, preset string)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if forum.Preset != preset || preset == "custom" || preset == "" {
|
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)
|
_ = fstore.Reload(forum.ID)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Replace this sorting mechanism with something a lot more efficient
|
// 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
|
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 {
|
func buildForumURL(slug string, fid int) string {
|
||||||
if slug == "" {
|
if slug == "" {
|
||||||
return "/forum/" + strconv.Itoa(fid)
|
return "/forum/" + strconv.Itoa(fid)
|
||||||
|
|
169
forum_store.go
169
forum_store.go
|
@ -27,14 +27,13 @@ type ForumStore interface {
|
||||||
LoadForums() error
|
LoadForums() error
|
||||||
DirtyGet(id int) *Forum
|
DirtyGet(id int) *Forum
|
||||||
Get(id int) (*Forum, error)
|
Get(id int) (*Forum, error)
|
||||||
GetCopy(id int) (Forum, error)
|
|
||||||
BypassGet(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
|
Reload(id int) error // ? - Should we move this to ForumCache? It might require us to do some unnecessary casting though
|
||||||
//Update(Forum) error
|
//Update(Forum) error
|
||||||
Delete(id int) error
|
Delete(id int) error
|
||||||
IncrementTopicCount(id int) error
|
AddTopic(tid int, uid int, fid int) error
|
||||||
DecrementTopicCount(id int) error
|
RemoveTopic(fid int) error
|
||||||
UpdateLastTopic(topicName string, tid int, username string, uid int, time string, fid int) error
|
UpdateLastTopic(tid int, uid int, fid int) error
|
||||||
Exists(id int) bool
|
Exists(id int) bool
|
||||||
GetAll() ([]*Forum, error)
|
GetAll() ([]*Forum, error)
|
||||||
GetAllIDs() ([]int, error)
|
GetAllIDs() ([]int, error)
|
||||||
|
@ -51,6 +50,7 @@ type ForumCache interface {
|
||||||
CacheGet(id int) (*Forum, error)
|
CacheGet(id int) (*Forum, error)
|
||||||
CacheSet(forum *Forum) error
|
CacheSet(forum *Forum) error
|
||||||
CacheDelete(id int)
|
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
|
// 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
|
forums sync.Map // map[int]*Forum
|
||||||
forumView atomic.Value // []*Forum
|
forumView atomic.Value // []*Forum
|
||||||
//fids []int
|
//fids []int
|
||||||
forumCount int
|
|
||||||
|
|
||||||
get *sql.Stmt
|
get *sql.Stmt
|
||||||
getAll *sql.Stmt
|
getAll *sql.Stmt
|
||||||
|
@ -94,10 +93,6 @@ func NewMemoryForumStore() *MemoryForumStore {
|
||||||
|
|
||||||
// TODO: Add support for subforums
|
// TODO: Add support for subforums
|
||||||
func (mfs *MemoryForumStore) LoadForums() error {
|
func (mfs *MemoryForumStore) LoadForums() error {
|
||||||
log.Print("Adding the uncategorised forum")
|
|
||||||
forumUpdateMutex.Lock()
|
|
||||||
defer forumUpdateMutex.Unlock()
|
|
||||||
|
|
||||||
var forumView []*Forum
|
var forumView []*Forum
|
||||||
addForum := func(forum *Forum) {
|
addForum := func(forum *Forum) {
|
||||||
mfs.forums.Store(forum.ID, forum)
|
mfs.forums.Store(forum.ID, forum)
|
||||||
|
@ -114,8 +109,8 @@ func (mfs *MemoryForumStore) LoadForums() error {
|
||||||
|
|
||||||
var i = 0
|
var i = 0
|
||||||
for ; rows.Next(); i++ {
|
for ; rows.Next(); i++ {
|
||||||
forum := Forum{ID: 0, Active: true, Preset: "all"}
|
forum := &Forum{ID: 0, Active: true, Preset: "all"}
|
||||||
err = rows.Scan(&forum.ID, &forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.ParentID, &forum.ParentType, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime)
|
err = rows.Scan(&forum.ID, &forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.ParentID, &forum.ParentType, &forum.TopicCount, &forum.LastTopicID, &forum.LastReplyerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -129,15 +124,27 @@ func (mfs *MemoryForumStore) LoadForums() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
forum.Link = buildForumURL(nameToSlug(forum.Name), forum.ID)
|
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)
|
mfs.forumView.Store(forumView)
|
||||||
return rows.Err()
|
return rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Hide social groups too
|
// TODO: Hide social groups too
|
||||||
|
// ? - Will this be hit a lot by plugin_socialgroups?
|
||||||
func (mfs *MemoryForumStore) rebuildView() {
|
func (mfs *MemoryForumStore) rebuildView() {
|
||||||
var forumView []*Forum
|
var forumView []*Forum
|
||||||
mfs.forums.Range(func(_ interface{}, value interface{}) bool {
|
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 == "" {
|
if !ok || fint.(*Forum).Name == "" {
|
||||||
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)
|
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.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 forum, err
|
||||||
}
|
}
|
||||||
return fint.(*Forum), nil
|
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) {
|
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)
|
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.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 {
|
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)
|
err := mfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
forum.Link = buildForumURL(nameToSlug(forum.Name), forum.ID)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,8 +317,6 @@ func (mfs *MemoryForumStore) Delete(id int) error {
|
||||||
if id == 1 {
|
if id == 1 {
|
||||||
return errors.New("You cannot delete the Reports forum")
|
return errors.New("You cannot delete the Reports forum")
|
||||||
}
|
}
|
||||||
forumUpdateMutex.Lock()
|
|
||||||
defer forumUpdateMutex.Unlock()
|
|
||||||
_, err := mfs.delete.Exec(id)
|
_, err := mfs.delete.Exec(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -291,53 +325,40 @@ func (mfs *MemoryForumStore) Delete(id int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ! Is this racey?
|
func (mfs *MemoryForumStore) AddTopic(tid int, uid int, fid int) error {
|
||||||
func (mfs *MemoryForumStore) IncrementTopicCount(id int) error {
|
_, err := updateForumCacheStmt.Exec(tid, uid, fid)
|
||||||
forum, err := mfs.Get(id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = addTopicsToForumStmt.Exec(1, id)
|
_, err = addTopicsToForumStmt.Exec(1, fid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
forum.TopicCount++
|
// TODO: Bypass the database and update this with a lock or an unsafe atomic swap
|
||||||
|
mfs.Reload(fid)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ! Is this racey?
|
// TODO: Update the forum cache with the latest topic
|
||||||
func (mfs *MemoryForumStore) DecrementTopicCount(id int) error {
|
func (mfs *MemoryForumStore) RemoveTopic(fid int) error {
|
||||||
forum, err := mfs.Get(id)
|
_, err := removeTopicsFromForumStmt.Exec(1, fid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = removeTopicsFromForumStmt.Exec(1, id)
|
// TODO: Bypass the database and update this with a lock or an unsafe atomic swap
|
||||||
if err != nil {
|
mfs.Reload(fid)
|
||||||
return err
|
|
||||||
}
|
|
||||||
forum.TopicCount--
|
|
||||||
return nil
|
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
|
// TODO: Have a pointer to the last topic rather than storing it on the forum itself
|
||||||
// ! Is this racey?
|
func (mfs *MemoryForumStore) UpdateLastTopic(tid int, uid int, fid int) error {
|
||||||
func (mfs *MemoryForumStore) UpdateLastTopic(topicName string, tid int, username string, uid int, time string, fid int) error {
|
_, err := updateForumCacheStmt.Exec(tid, uid, fid)
|
||||||
forum, err := mfs.Get(fid)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// TODO: Bypass the database and update this with a lock or an unsafe atomic swap
|
||||||
_, err = updateForumCacheStmt.Exec(topicName, tid, username, uid, fid)
|
mfs.Reload(fid)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
forum.LastTopic = topicName
|
|
||||||
forum.LastTopicID = tid
|
|
||||||
forum.LastReplyer = username
|
|
||||||
forum.LastReplyerID = uid
|
|
||||||
forum.LastTopicTime = time
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,19 +375,25 @@ func (mfs *MemoryForumStore) Create(forumName string, forumDesc string, active b
|
||||||
}
|
}
|
||||||
fid := int(fid64)
|
fid := int(fid64)
|
||||||
|
|
||||||
mfs.forums.Store(fid, &Forum{fid, buildForumURL(nameToSlug(forumName), fid), forumName, forumDesc, active, preset, 0, "", 0, "", "", 0, "", 0, ""})
|
err = mfs.Reload(fid)
|
||||||
mfs.forumCount++
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Add a GroupStore. How would it interact with the ForumStore?
|
|
||||||
permmapToQuery(presetToPermmap(preset), fid)
|
permmapToQuery(presetToPermmap(preset), fid)
|
||||||
forumCreateMutex.Unlock()
|
forumCreateMutex.Unlock()
|
||||||
|
|
||||||
if active {
|
|
||||||
mfs.rebuildView()
|
|
||||||
}
|
|
||||||
return fid, nil
|
return fid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ! 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?
|
// 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
|
// GetGlobalCount returns the total number of forums
|
||||||
func (mfs *MemoryForumStore) GetGlobalCount() (fcount int) {
|
func (mfs *MemoryForumStore) GetGlobalCount() (fcount int) {
|
||||||
|
|
|
@ -186,7 +186,7 @@ func _gen_mysql() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Print("Preparing getForums statement.")
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -534,7 +534,7 @@ func _gen_mysql() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Print("Preparing updateForumCache statement.")
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,7 @@ func _gen_pgsql() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Print("Preparing updateForumCache statement.")
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,14 +53,6 @@ func gloinit() error {
|
||||||
log.Fatal(err)
|
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.")
|
log.Print("Loading the static files.")
|
||||||
err = initStaticFiles()
|
err = initStaticFiles()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -548,7 +540,7 @@ func BenchmarkQueriesSerial(b *testing.B) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
var replyItem Reply
|
var replyItem ReplyUser
|
||||||
var isSuperAdmin bool
|
var isSuperAdmin bool
|
||||||
var group int
|
var group int
|
||||||
b.Run("topic_replies_scan", func(b *testing.B) {
|
b.Run("topic_replies_scan", func(b *testing.B) {
|
||||||
|
|
5
group.go
5
group.go
|
@ -11,6 +11,7 @@ type GroupAdmin struct {
|
||||||
CanDelete bool
|
CanDelete bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ! Fix the data races
|
||||||
type Group struct {
|
type Group struct {
|
||||||
ID int
|
ID int
|
||||||
Name string
|
Name string
|
||||||
|
@ -25,3 +26,7 @@ type Group struct {
|
||||||
Forums []ForumPerms
|
Forums []ForumPerms
|
||||||
CanSee []int // The IDs of the forums this group can see
|
CanSee []int // The IDs of the forums this group can see
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (group *Group) Copy() Group {
|
||||||
|
return *group
|
||||||
|
}
|
||||||
|
|
8
main.go
8
main.go
|
@ -82,14 +82,6 @@ func main() {
|
||||||
log.Fatal(err)
|
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.")
|
log.Print("Loading the static files.")
|
||||||
err = initStaticFiles()
|
err = initStaticFiles()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ? - Should we add a new permission or permission zone (like per-forum permissions) specifically for profile comment creation
|
// ? - 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?
|
// Do a bulk forum fetch, just in case it's the SqlForumStore?
|
||||||
forum := fstore.DirtyGet(ffid)
|
forum := fstore.DirtyGet(ffid)
|
||||||
if forum.Name != "" && forum.Active {
|
if forum.Name != "" && forum.Active {
|
||||||
fcopy := *forum
|
fcopy := forum.Copy()
|
||||||
if hooks["topic_create_frow_assign"] != nil {
|
if hooks["topic_create_frow_assign"] != nil {
|
||||||
// TODO: Add the skip feature to all the other row based hooks?
|
// TODO: Add the skip feature to all the other row based hooks?
|
||||||
if runHook("topic_create_frow_assign", &fcopy).(bool) {
|
if runHook("topic_create_frow_assign", &fcopy).(bool) {
|
||||||
|
@ -144,12 +143,6 @@ func routeTopicCreateSubmit(w http.ResponseWriter, r *http.Request, user User) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = fstore.IncrementTopicCount(fid)
|
|
||||||
if err != nil {
|
|
||||||
InternalError(err, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = addSubscriptionStmt.Exec(user.ID, lastID, "topic")
|
_, err = addSubscriptionStmt.Exec(user.ID, lastID, "topic")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
InternalError(err, w)
|
InternalError(err, w)
|
||||||
|
@ -163,7 +156,7 @@ func routeTopicCreateSubmit(w http.ResponseWriter, r *http.Request, user User) {
|
||||||
return
|
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 {
|
if err != nil && err != ErrNoRows {
|
||||||
InternalError(err, w)
|
InternalError(err, w)
|
||||||
}
|
}
|
||||||
|
@ -219,7 +212,14 @@ func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) {
|
||||||
InternalError(err, w)
|
InternalError(err, w)
|
||||||
return
|
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 {
|
if err != nil && err != ErrNoRows {
|
||||||
InternalError(err, w)
|
InternalError(err, w)
|
||||||
return
|
return
|
||||||
|
@ -247,12 +247,6 @@ func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) {
|
||||||
go notifyWatchers(lastID)
|
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)
|
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
|
||||||
err = user.increasePostStats(wcount, false)
|
err = user.increasePostStats(wcount, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -629,7 +623,7 @@ func routeReportSubmit(w http.ResponseWriter, r *http.Request, user User, sitemI
|
||||||
InternalError(err, w)
|
InternalError(err, w)
|
||||||
return
|
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 {
|
if err != nil && err != ErrNoRows {
|
||||||
InternalError(err, w)
|
InternalError(err, w)
|
||||||
return
|
return
|
||||||
|
|
17
misc_test.go
17
misc_test.go
|
@ -269,19 +269,12 @@ func TestForumStore(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
forum, err = fstore.Get(0)
|
forum, err = fstore.Get(0)
|
||||||
if err == ErrNoRows {
|
if err == nil {
|
||||||
t.Error("Couldn't find FID #0")
|
t.Error("FID #0 shouldn't exist")
|
||||||
} else if err != nil {
|
} else if err != ErrNoRows {
|
||||||
t.Fatal(err)
|
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)
|
forum, err = fstore.Get(1)
|
||||||
if err == ErrNoRows {
|
if err == ErrNoRows {
|
||||||
t.Error("Couldn't find FID #1")
|
t.Error("Couldn't find FID #1")
|
||||||
|
@ -311,8 +304,8 @@ func TestForumStore(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = fstore.Exists(0)
|
ok = fstore.Exists(0)
|
||||||
if !ok {
|
if ok {
|
||||||
t.Error("FID #0 should exist")
|
t.Error("FID #0 shouldn't exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = fstore.Exists(1)
|
ok = fstore.Exists(1)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
//"log"
|
//"log"
|
||||||
//"fmt"
|
//"fmt"
|
||||||
"html"
|
"html"
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -26,7 +27,7 @@ func routeEditTopic(w http.ResponseWriter, r *http.Request, user User) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
oldTopic, err := topics.Get(tid)
|
topic, err := topics.Get(tid)
|
||||||
if err == ErrNoRows {
|
if err == ErrNoRows {
|
||||||
PreErrorJSQ("The topic you tried to edit doesn't exist.", w, r, isJs)
|
PreErrorJSQ("The topic you tried to edit doesn't exist.", w, r, isJs)
|
||||||
return
|
return
|
||||||
|
@ -36,7 +37,7 @@ func routeEditTopic(w http.ResponseWriter, r *http.Request, user User) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add hooks to make use of headerLite
|
// TODO: Add hooks to make use of headerLite
|
||||||
_, ok := SimpleForumUserCheck(w, r, &user, oldTopic.ParentID)
|
_, ok := SimpleForumUserCheck(w, r, &user, topic.ParentID)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -47,25 +48,20 @@ func routeEditTopic(w http.ResponseWriter, r *http.Request, user User) {
|
||||||
|
|
||||||
topicName := r.PostFormValue("topic_name")
|
topicName := r.PostFormValue("topic_name")
|
||||||
topicContent := html.EscapeString(r.PostFormValue("topic_content"))
|
topicContent := html.EscapeString(r.PostFormValue("topic_content"))
|
||||||
|
log.Print("topicContent ", topicContent)
|
||||||
|
|
||||||
// TODO: Move this bit to the TopicStore
|
err = topic.Update(topicName, topicContent)
|
||||||
_, err = editTopicStmt.Exec(topicName, preparseMessage(topicContent), parseMessage(html.EscapeString(preparseMessage(topicContent))), tid)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
InternalErrorJSQ(err, w, r, isJs)
|
InternalErrorJSQ(err, w, r, isJs)
|
||||||
return
|
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 {
|
if err != nil && err != ErrNoRows {
|
||||||
InternalError(err, w)
|
InternalErrorJSQ(err, w, r, isJs)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tcache, ok := topics.(TopicCache)
|
|
||||||
if ok {
|
|
||||||
tcache.CacheRemove(oldTopic.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isJs {
|
if !isJs {
|
||||||
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
|
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
|
@ -26,11 +26,8 @@ CREATE TABLE `forums`(
|
||||||
`preset` varchar(100) DEFAULT '' not null,
|
`preset` varchar(100) DEFAULT '' not null,
|
||||||
`parentID` int DEFAULT 0 not null, /* TODO: Add support for subforums */
|
`parentID` int DEFAULT 0 not null, /* TODO: Add support for subforums */
|
||||||
`parentType` varchar(50) DEFAULT '' not null,
|
`parentType` varchar(50) DEFAULT '' not null,
|
||||||
`lastTopic` varchar(100) DEFAULT '' not null,
|
|
||||||
`lastTopicID` int DEFAULT 0 not null,
|
`lastTopicID` int DEFAULT 0 not null,
|
||||||
`lastReplyer` varchar(100) DEFAULT '' not null,
|
|
||||||
`lastReplyerID` int DEFAULT 0 not null,
|
`lastReplyerID` int DEFAULT 0 not null,
|
||||||
`lastTopicTime` datetime not null,
|
|
||||||
primary key(`fid`)
|
primary key(`fid`)
|
||||||
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
|
) 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 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`,`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 (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}');
|
INSERT INTO forums_permissions(`gid`,`fid`,`permissions`) VALUES (2,1,'{"ViewTopic":true,"CreateReply":true,"CloseTopic":true}');
|
||||||
|
|
6
pages.go
6
pages.go
|
@ -54,7 +54,7 @@ type TopicPage struct {
|
||||||
Title string
|
Title string
|
||||||
CurrentUser User
|
CurrentUser User
|
||||||
Header *HeaderVars
|
Header *HeaderVars
|
||||||
ItemList []Reply
|
ItemList []ReplyUser
|
||||||
Topic TopicUser
|
Topic TopicUser
|
||||||
Page int
|
Page int
|
||||||
LastPage int
|
LastPage int
|
||||||
|
@ -72,7 +72,7 @@ type ForumPage struct {
|
||||||
CurrentUser User
|
CurrentUser User
|
||||||
Header *HeaderVars
|
Header *HeaderVars
|
||||||
ItemList []*TopicsRow
|
ItemList []*TopicsRow
|
||||||
Forum Forum
|
Forum *Forum
|
||||||
Page int
|
Page int
|
||||||
LastPage int
|
LastPage int
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ type ProfilePage struct {
|
||||||
Title string
|
Title string
|
||||||
CurrentUser User
|
CurrentUser User
|
||||||
Header *HeaderVars
|
Header *HeaderVars
|
||||||
ItemList []Reply
|
ItemList []ReplyUser
|
||||||
ProfileOwner User
|
ProfileOwner User
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
|
@ -56,7 +56,7 @@ type SocialGroupPage struct {
|
||||||
CurrentUser User
|
CurrentUser User
|
||||||
Header *HeaderVars
|
Header *HeaderVars
|
||||||
ItemList []*TopicsRow
|
ItemList []*TopicsRow
|
||||||
Forum Forum
|
Forum *Forum
|
||||||
SocialGroup *SocialGroup
|
SocialGroup *SocialGroup
|
||||||
Page int
|
Page int
|
||||||
LastPage int
|
LastPage int
|
||||||
|
|
|
@ -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 + "'")
|
||||||
|
}
|
|
@ -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("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", "")
|
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("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 = ?")
|
adapter.SimpleUpdate("addLikesToTopic", "topics", "likeCount = likeCount + ?", "tid = ?")
|
||||||
|
|
||||||
|
|
17
reply.go
17
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?
|
// ? - 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
|
ID int
|
||||||
ParentID int
|
ParentID int
|
||||||
Content string
|
Content string
|
||||||
|
@ -36,7 +35,7 @@ type Reply struct /* Should probably rename this to ReplyUser and rename ReplySh
|
||||||
ActionIcon string
|
ActionIcon string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReplyShort struct {
|
type Reply struct {
|
||||||
ID int
|
ID int
|
||||||
ParentID int
|
ParentID int
|
||||||
Content string
|
Content string
|
||||||
|
@ -51,14 +50,18 @@ type ReplyShort struct {
|
||||||
LikeCount int
|
LikeCount int
|
||||||
}
|
}
|
||||||
|
|
||||||
func getReply(id int) (*ReplyShort, error) {
|
func (reply *Reply) Copy() Reply {
|
||||||
reply := ReplyShort{ID: id}
|
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)
|
err := getReplyStmt.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IPAddress, &reply.LikeCount)
|
||||||
return &reply, err
|
return &reply, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUserReply(id int) (*ReplyShort, error) {
|
func getUserReply(id int) (*Reply, error) {
|
||||||
reply := ReplyShort{ID: id}
|
reply := Reply{ID: id}
|
||||||
err := getUserReplyStmt.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IPAddress)
|
err := getUserReplyStmt.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IPAddress)
|
||||||
return &reply, err
|
return &reply, err
|
||||||
}
|
}
|
||||||
|
|
42
routes.go
42
routes.go
|
@ -159,6 +159,7 @@ func routeTopics(w http.ResponseWriter, r *http.Request, user User) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Make CanSee a method on *Group with a canSee field?
|
||||||
var canSee []int
|
var canSee []int
|
||||||
if user.IsSuperAdmin {
|
if user.IsSuperAdmin {
|
||||||
canSee, err = fstore.GetAllVisibleIDs()
|
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]
|
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 preRenderHooks["pre_render_view_forum"] != nil {
|
||||||
if runPreRenderHook("pre_render_view_forum", w, r, &user, &pi) {
|
if runPreRenderHook("pre_render_view_forum", w, r, &user, &pi) {
|
||||||
return
|
return
|
||||||
|
@ -417,16 +418,22 @@ func routeForums(w http.ResponseWriter, r *http.Request, user User) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, fid := range canSee {
|
for _, fid := range canSee {
|
||||||
//log.Print(forums[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)
|
var forum = fstore.DirtyGet(fid).Copy()
|
||||||
if forum.ParentID == 0 && forum.Name != "" && forum.Active {
|
if forum.ParentID == 0 && forum.Name != "" && forum.Active {
|
||||||
if forum.LastTopicID != 0 {
|
if forum.LastTopicID != 0 {
|
||||||
forum.LastTopicTime, err = relativeTime(forum.LastTopicTime)
|
//topic, user := forum.GetLast()
|
||||||
if err != nil {
|
//if topic.ID != 0 && user.ID != 0 {
|
||||||
InternalError(err, w)
|
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 {
|
} else {
|
||||||
forum.LastTopic = "None"
|
|
||||||
forum.LastTopicTime = ""
|
forum.LastTopicTime = ""
|
||||||
}
|
}
|
||||||
if hooks["forums_frow_assign"] != nil {
|
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) {
|
func routeTopicID(w http.ResponseWriter, r *http.Request, user User) {
|
||||||
var err error
|
var err error
|
||||||
var page, offset int
|
var page, offset int
|
||||||
var replyList []Reply
|
var replyList []ReplyUser
|
||||||
|
|
||||||
page, _ = strconv.Atoi(r.FormValue("page"))
|
page, _ = strconv.Atoi(r.FormValue("page"))
|
||||||
|
|
||||||
|
@ -465,7 +472,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the topic...
|
// Get the topic...
|
||||||
topic, err := getTopicuser(tid)
|
topic, err := getTopicUser(tid)
|
||||||
if err == ErrNoRows {
|
if err == ErrNoRows {
|
||||||
NotFound(w, r)
|
NotFound(w, r)
|
||||||
return
|
return
|
||||||
|
@ -488,7 +495,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) {
|
||||||
|
|
||||||
BuildWidgets("view_topic", &topic, headerVars, r)
|
BuildWidgets("view_topic", &topic, headerVars, r)
|
||||||
|
|
||||||
topic.Content = parseMessage(topic.Content)
|
topic.ContentHTML = parseMessage(topic.Content)
|
||||||
topic.ContentLines = strings.Count(topic.Content, "\n")
|
topic.ContentLines = strings.Count(topic.Content, "\n")
|
||||||
|
|
||||||
// We don't want users posting in locked topics...
|
// 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
|
page = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tpage := TopicPage{topic.Title, user, headerVars, replyList, topic, page, lastPage}
|
||||||
|
|
||||||
// Get the replies..
|
// Get the replies..
|
||||||
rows, err := getTopicRepliesOffsetStmt.Query(topic.ID, offset, config.ItemsPerPage)
|
rows, err := getTopicRepliesOffsetStmt.Query(topic.ID, offset, config.ItemsPerPage)
|
||||||
if err == ErrNoRows {
|
if err == ErrNoRows {
|
||||||
|
@ -554,7 +563,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) {
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
replyItem := Reply{ClassName: ""}
|
replyItem := ReplyUser{ClassName: ""}
|
||||||
for rows.Next() {
|
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)
|
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 {
|
if err != nil {
|
||||||
|
@ -628,9 +637,8 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) {
|
||||||
}
|
}
|
||||||
replyItem.Liked = false
|
replyItem.Liked = false
|
||||||
|
|
||||||
// TODO: Rename this to topic_rrow_assign
|
if vhooks["topic_reply_row_assign"] != nil {
|
||||||
if hooks["rrow_assign"] != nil {
|
runVhook("topic_reply_row_assign", &tpage, &replyItem)
|
||||||
runHook("rrow_assign", &replyItem)
|
|
||||||
}
|
}
|
||||||
replyList = append(replyList, replyItem)
|
replyList = append(replyList, replyItem)
|
||||||
}
|
}
|
||||||
|
@ -640,7 +648,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tpage := TopicPage{topic.Title, user, headerVars, replyList, topic, page, lastPage}
|
tpage.ItemList = replyList
|
||||||
if preRenderHooks["pre_render_view_topic"] != nil {
|
if preRenderHooks["pre_render_view_topic"] != nil {
|
||||||
if runPreRenderHook("pre_render_view_topic", w, r, &user, &tpage) {
|
if runPreRenderHook("pre_render_view_topic", w, r, &user, &tpage) {
|
||||||
return
|
return
|
||||||
|
@ -658,7 +666,7 @@ func routeProfile(w http.ResponseWriter, r *http.Request, user User) {
|
||||||
var err error
|
var err error
|
||||||
var replyContent, replyCreatedByName, replyCreatedAt, replyAvatar, replyTag, replyClassName string
|
var replyContent, replyCreatedByName, replyCreatedAt, replyAvatar, replyTag, replyClassName string
|
||||||
var rid, replyCreatedBy, replyLastEdit, replyLastEditBy, replyLines, replyGroup int
|
var rid, replyCreatedBy, replyLastEdit, replyLastEditBy, replyLines, replyGroup int
|
||||||
var replyList []Reply
|
var replyList []ReplyUser
|
||||||
|
|
||||||
// SEO URLs...
|
// SEO URLs...
|
||||||
halves := strings.Split(r.URL.Path[len("/user/"):], ".")
|
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
|
// 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()
|
err = rows.Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -73,7 +73,7 @@ w.Write(forums_0)
|
||||||
if len(tmpl_forums_vars.ItemList) != 0 {
|
if len(tmpl_forums_vars.ItemList) != 0 {
|
||||||
for _, item := range tmpl_forums_vars.ItemList {
|
for _, item := range tmpl_forums_vars.ItemList {
|
||||||
w.Write(forums_1)
|
w.Write(forums_1)
|
||||||
if item.Desc != "" || item.LastTopicTime != "" {
|
if item.Desc != "" || item.LastTopic.Title != "" {
|
||||||
w.Write(forums_2)
|
w.Write(forums_2)
|
||||||
}
|
}
|
||||||
w.Write(forums_3)
|
w.Write(forums_3)
|
||||||
|
@ -93,21 +93,25 @@ w.Write([]byte(item.Name))
|
||||||
w.Write(forums_10)
|
w.Write(forums_10)
|
||||||
}
|
}
|
||||||
w.Write(forums_11)
|
w.Write(forums_11)
|
||||||
w.Write([]byte(item.LastTopicLink))
|
w.Write([]byte(item.LastTopic.Link))
|
||||||
w.Write(forums_12)
|
w.Write(forums_12)
|
||||||
w.Write([]byte(item.LastTopic))
|
if item.LastTopic.Title != "" {
|
||||||
|
w.Write([]byte(item.LastTopic.Title))
|
||||||
|
} else {
|
||||||
w.Write(forums_13)
|
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)
|
w.Write(forums_16)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
w.Write(forums_17)
|
w.Write(forums_17)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
w.Write(forums_18)
|
w.Write(forums_18)
|
||||||
|
}
|
||||||
|
w.Write(forums_19)
|
||||||
w.Write(footer_0)
|
w.Write(footer_0)
|
||||||
if len(tmpl_forums_vars.Header.Themes) != 0 {
|
if len(tmpl_forums_vars.Header.Themes) != 0 {
|
||||||
for _, item := range tmpl_forums_vars.Header.Themes {
|
for _, item := range tmpl_forums_vars.Header.Themes {
|
||||||
|
|
|
@ -106,9 +106,9 @@ func compileTemplates() error {
|
||||||
|
|
||||||
log.Print("Compiling the templates")
|
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}
|
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
|
var replyList []ReplyUser
|
||||||
replyList = append(replyList, Reply{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", config.DefaultGroup, "", 0, 0, "", "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, "", ""})
|
replyList = append(replyList, 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)
|
var varList = make(map[string]VarItem)
|
||||||
tpage := TopicPage{"Title", user, headerVars, replyList, topic, 1, 1}
|
tpage := TopicPage{"Title", user, headerVars, replyList, topic, 1, 1}
|
||||||
|
@ -135,6 +135,7 @@ func compileTemplates() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, forum := range forums {
|
for _, forum := range forums {
|
||||||
|
//log.Printf("*forum %+v\n", *forum)
|
||||||
forumList = append(forumList, *forum)
|
forumList = append(forumList, *forum)
|
||||||
}
|
}
|
||||||
varList = make(map[string]VarItem)
|
varList = make(map[string]VarItem)
|
||||||
|
@ -154,7 +155,7 @@ func compileTemplates() error {
|
||||||
|
|
||||||
//var topicList []TopicUser
|
//var topicList []TopicUser
|
||||||
//topicList = append(topicList,TopicUser{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"classname","","admin-fred","Admin Fred",config.DefaultGroup,"",0,"","","","",58,false})
|
//topicList = append(topicList,TopicUser{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"classname","","admin-fred","Admin Fred",config.DefaultGroup,"",0,"","","","",58,false})
|
||||||
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}
|
forumPage := ForumPage{"General Forum", user, headerVars, topicsList, forumItem, 1, 1}
|
||||||
forumTmpl, err := c.compileTemplate("forum.html", "templates/", "ForumPage", forumPage, varList)
|
forumTmpl, err := c.compileTemplate("forum.html", "templates/", "ForumPage", forumPage, varList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -185,50 +185,52 @@ var topic_63 = []byte(`), url(/static/post-avatar-bg.jpg);background-position: 0
|
||||||
var topic_64 = []byte(`-1`)
|
var topic_64 = []byte(`-1`)
|
||||||
var topic_65 = []byte(`0px;background-repeat:no-repeat, repeat-y;`)
|
var topic_65 = []byte(`0px;background-repeat:no-repeat, repeat-y;`)
|
||||||
var topic_66 = []byte(`">
|
var topic_66 = []byte(`">
|
||||||
|
`)
|
||||||
|
var topic_67 = []byte(`
|
||||||
<p class="editable_block user_content" style="margin:0;padding:0;">`)
|
<p class="editable_block user_content" style="margin:0;padding:0;">`)
|
||||||
var topic_67 = []byte(`</p>
|
var topic_68 = []byte(`</p>
|
||||||
|
|
||||||
<span class="controls">
|
<span class="controls">
|
||||||
|
|
||||||
<a href="`)
|
<a href="`)
|
||||||
var topic_68 = []byte(`" class="username real_username">`)
|
var topic_69 = []byte(`" class="username real_username">`)
|
||||||
var topic_69 = []byte(`</a>
|
var topic_70 = []byte(`</a>
|
||||||
`)
|
`)
|
||||||
var topic_70 = []byte(`<a href="/reply/like/submit/`)
|
var topic_71 = []byte(`<a href="/reply/like/submit/`)
|
||||||
var topic_71 = []byte(`" class="mod_button" title="Love it" style="color:#202020;"><button class="username like_label"`)
|
var topic_72 = []byte(`" class="mod_button" title="Love it" style="color:#202020;"><button class="username like_label"`)
|
||||||
var topic_72 = []byte(` style="background-color:#D6FFD6;"`)
|
var topic_73 = []byte(` style="background-color:#D6FFD6;"`)
|
||||||
var topic_73 = []byte(`></button></a>`)
|
var topic_74 = []byte(`></button></a>`)
|
||||||
var topic_74 = []byte(`<a href="/reply/edit/submit/`)
|
var topic_75 = []byte(`<a href="/reply/edit/submit/`)
|
||||||
var topic_75 = []byte(`" class="mod_button" title="Edit Reply"><button class="username edit_item edit_label"></button></a>`)
|
var topic_76 = []byte(`" class="mod_button" title="Edit Reply"><button class="username edit_item edit_label"></button></a>`)
|
||||||
var topic_76 = []byte(`<a href="/reply/delete/submit/`)
|
var topic_77 = []byte(`<a href="/reply/delete/submit/`)
|
||||||
var topic_77 = []byte(`" class="mod_button" title="Delete Reply"><button class="username delete_item trash_label"></button></a>`)
|
var topic_78 = []byte(`" class="mod_button" title="Delete Reply"><button class="username delete_item trash_label"></button></a>`)
|
||||||
var topic_78 = []byte(`<a class="mod_button" href='/users/ips/?ip=`)
|
var topic_79 = []byte(`<a class="mod_button" href='/users/ips/?ip=`)
|
||||||
var topic_79 = []byte(`' style="font-weight:normal;" title="View IP"><button class="username ip_label"></button></a>`)
|
var topic_80 = []byte(`' style="font-weight:normal;" title="View IP"><button class="username ip_label"></button></a>`)
|
||||||
var topic_80 = []byte(`
|
var topic_81 = []byte(`
|
||||||
<a href="/report/submit/`)
|
<a href="/report/submit/`)
|
||||||
var topic_81 = []byte(`?session=`)
|
var topic_82 = []byte(`?session=`)
|
||||||
var topic_82 = []byte(`&type=reply" class="mod_button report_item" title="Flag Reply"><button class="username report_item flag_label"></button></a>
|
var topic_83 = []byte(`&type=reply" class="mod_button report_item" title="Flag Reply"><button class="username report_item flag_label"></button></a>
|
||||||
|
|
||||||
`)
|
`)
|
||||||
var topic_83 = []byte(`<a class="username hide_on_micro like_count">`)
|
var topic_84 = []byte(`<a class="username hide_on_micro like_count">`)
|
||||||
var topic_84 = []byte(`</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>`)
|
var topic_85 = []byte(`</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>`)
|
||||||
var topic_85 = []byte(`<a class="username hide_on_micro user_tag">`)
|
var topic_86 = []byte(`<a class="username hide_on_micro user_tag">`)
|
||||||
var topic_86 = []byte(`</a>`)
|
var topic_87 = []byte(`</a>`)
|
||||||
var topic_87 = []byte(`<a class="username hide_on_micro level">`)
|
var topic_88 = []byte(`<a class="username hide_on_micro level">`)
|
||||||
var topic_88 = []byte(`</a><a class="username hide_on_micro level_label" style="float:right;" title="Level"></a>`)
|
var topic_89 = []byte(`</a><a class="username hide_on_micro level_label" style="float:right;" title="Level"></a>`)
|
||||||
var topic_89 = []byte(`
|
var topic_90 = []byte(`
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
</article>
|
</article>
|
||||||
`)
|
`)
|
||||||
var topic_90 = []byte(`</div>
|
var topic_91 = []byte(`</div>
|
||||||
|
|
||||||
`)
|
`)
|
||||||
var topic_91 = []byte(`
|
var topic_92 = []byte(`
|
||||||
<div class="rowblock topic_reply_form">
|
<div class="rowblock topic_reply_form">
|
||||||
<form action="/reply/create/" method="post">
|
<form action="/reply/create/" method="post">
|
||||||
<input name="tid" value='`)
|
<input name="tid" value='`)
|
||||||
var topic_92 = []byte(`' type="hidden" />
|
var topic_93 = []byte(`' type="hidden" />
|
||||||
<div class="formrow real_first_child">
|
<div class="formrow real_first_child">
|
||||||
<div class="formitem"><textarea name="reply-content" placeholder="Insert reply here" required></textarea></div>
|
<div class="formitem"><textarea name="reply-content" placeholder="Insert reply here" required></textarea></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -238,7 +240,7 @@ var topic_92 = []byte(`' type="hidden" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
`)
|
`)
|
||||||
var topic_93 = []byte(`
|
var topic_94 = []byte(`
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
@ -632,17 +634,18 @@ var forums_11 = []byte(`
|
||||||
<span style="float: right;">
|
<span style="float: right;">
|
||||||
<a href="`)
|
<a href="`)
|
||||||
var forums_12 = []byte(`" style="float: right;font-size: 14px;">`)
|
var forums_12 = []byte(`" style="float: right;font-size: 14px;">`)
|
||||||
var forums_13 = []byte(`</a>
|
var forums_13 = []byte(`None`)
|
||||||
|
var forums_14 = []byte(`</a>
|
||||||
`)
|
`)
|
||||||
var forums_14 = []byte(`<br /><span class="rowsmall">`)
|
var forums_15 = []byte(`<br /><span class="rowsmall">`)
|
||||||
var forums_15 = []byte(`</span>`)
|
var forums_16 = []byte(`</span>`)
|
||||||
var forums_16 = []byte(`
|
var forums_17 = []byte(`
|
||||||
</span>
|
</span>
|
||||||
<div style="clear: both;"></div>
|
<div style="clear: both;"></div>
|
||||||
</div>
|
</div>
|
||||||
`)
|
`)
|
||||||
var forums_17 = []byte(`<div class="rowitem passive">You don't have access to any forums.</div>`)
|
var forums_18 = []byte(`<div class="rowitem passive">You don't have access to any forums.</div>`)
|
||||||
var forums_18 = []byte(`
|
var forums_19 = []byte(`
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
@ -653,7 +656,7 @@ var topics_0 = []byte(`
|
||||||
<div class="rowblock rowhead">
|
<div class="rowblock rowhead">
|
||||||
<div class="rowitem"><h1>Topic List</h1></div>
|
<div class="rowitem"><h1>Topic List</h1></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="topic_list" class="rowblock topic_list">
|
<div id="topic_list" class="rowblock topic_list" aria-label="The main topic list">
|
||||||
`)
|
`)
|
||||||
var topics_1 = []byte(`<div class="rowitem topic_left passive datarow `)
|
var topics_1 = []byte(`<div class="rowitem topic_left passive datarow `)
|
||||||
var topics_2 = []byte(`topic_sticky`)
|
var topics_2 = []byte(`topic_sticky`)
|
||||||
|
|
|
@ -123,7 +123,7 @@ w.Write(topic_22)
|
||||||
w.Write(topic_23)
|
w.Write(topic_23)
|
||||||
}
|
}
|
||||||
w.Write(topic_24)
|
w.Write(topic_24)
|
||||||
w.Write([]byte(tmpl_topic_vars.Topic.Content))
|
w.Write([]byte(tmpl_topic_vars.Topic.ContentHTML))
|
||||||
w.Write(topic_25)
|
w.Write(topic_25)
|
||||||
w.Write([]byte(tmpl_topic_vars.Topic.Content))
|
w.Write([]byte(tmpl_topic_vars.Topic.Content))
|
||||||
w.Write(topic_26)
|
w.Write(topic_26)
|
||||||
|
@ -219,66 +219,67 @@ w.Write(topic_64)
|
||||||
w.Write(topic_65)
|
w.Write(topic_65)
|
||||||
}
|
}
|
||||||
w.Write(topic_66)
|
w.Write(topic_66)
|
||||||
w.Write([]byte(item.ContentHtml))
|
|
||||||
w.Write(topic_67)
|
w.Write(topic_67)
|
||||||
w.Write([]byte(item.UserLink))
|
w.Write([]byte(item.ContentHtml))
|
||||||
w.Write(topic_68)
|
w.Write(topic_68)
|
||||||
w.Write([]byte(item.CreatedByName))
|
w.Write([]byte(item.UserLink))
|
||||||
w.Write(topic_69)
|
w.Write(topic_69)
|
||||||
if tmpl_topic_vars.CurrentUser.Perms.LikeItem {
|
w.Write([]byte(item.CreatedByName))
|
||||||
w.Write(topic_70)
|
w.Write(topic_70)
|
||||||
w.Write([]byte(strconv.Itoa(item.ID)))
|
if tmpl_topic_vars.CurrentUser.Perms.LikeItem {
|
||||||
w.Write(topic_71)
|
w.Write(topic_71)
|
||||||
if item.Liked {
|
w.Write([]byte(strconv.Itoa(item.ID)))
|
||||||
w.Write(topic_72)
|
w.Write(topic_72)
|
||||||
}
|
if item.Liked {
|
||||||
w.Write(topic_73)
|
w.Write(topic_73)
|
||||||
}
|
}
|
||||||
if tmpl_topic_vars.CurrentUser.Perms.EditReply {
|
|
||||||
w.Write(topic_74)
|
w.Write(topic_74)
|
||||||
w.Write([]byte(strconv.Itoa(item.ID)))
|
}
|
||||||
|
if tmpl_topic_vars.CurrentUser.Perms.EditReply {
|
||||||
w.Write(topic_75)
|
w.Write(topic_75)
|
||||||
|
w.Write([]byte(strconv.Itoa(item.ID)))
|
||||||
|
w.Write(topic_76)
|
||||||
}
|
}
|
||||||
if tmpl_topic_vars.CurrentUser.Perms.DeleteReply {
|
if tmpl_topic_vars.CurrentUser.Perms.DeleteReply {
|
||||||
w.Write(topic_76)
|
|
||||||
w.Write([]byte(strconv.Itoa(item.ID)))
|
|
||||||
w.Write(topic_77)
|
w.Write(topic_77)
|
||||||
|
w.Write([]byte(strconv.Itoa(item.ID)))
|
||||||
|
w.Write(topic_78)
|
||||||
}
|
}
|
||||||
if tmpl_topic_vars.CurrentUser.Perms.ViewIPs {
|
if tmpl_topic_vars.CurrentUser.Perms.ViewIPs {
|
||||||
w.Write(topic_78)
|
|
||||||
w.Write([]byte(item.IPAddress))
|
|
||||||
w.Write(topic_79)
|
w.Write(topic_79)
|
||||||
}
|
w.Write([]byte(item.IPAddress))
|
||||||
w.Write(topic_80)
|
w.Write(topic_80)
|
||||||
w.Write([]byte(strconv.Itoa(item.ID)))
|
}
|
||||||
w.Write(topic_81)
|
w.Write(topic_81)
|
||||||
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
|
w.Write([]byte(strconv.Itoa(item.ID)))
|
||||||
w.Write(topic_82)
|
w.Write(topic_82)
|
||||||
if item.LikeCount > 0 {
|
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
|
||||||
w.Write(topic_83)
|
w.Write(topic_83)
|
||||||
w.Write([]byte(strconv.Itoa(item.LikeCount)))
|
if item.LikeCount > 0 {
|
||||||
w.Write(topic_84)
|
w.Write(topic_84)
|
||||||
|
w.Write([]byte(strconv.Itoa(item.LikeCount)))
|
||||||
|
w.Write(topic_85)
|
||||||
}
|
}
|
||||||
if item.Tag != "" {
|
if item.Tag != "" {
|
||||||
w.Write(topic_85)
|
|
||||||
w.Write([]byte(item.Tag))
|
|
||||||
w.Write(topic_86)
|
w.Write(topic_86)
|
||||||
} else {
|
w.Write([]byte(item.Tag))
|
||||||
w.Write(topic_87)
|
w.Write(topic_87)
|
||||||
w.Write([]byte(strconv.Itoa(item.Level)))
|
} else {
|
||||||
w.Write(topic_88)
|
w.Write(topic_88)
|
||||||
}
|
w.Write([]byte(strconv.Itoa(item.Level)))
|
||||||
w.Write(topic_89)
|
w.Write(topic_89)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
w.Write(topic_90)
|
w.Write(topic_90)
|
||||||
if tmpl_topic_vars.CurrentUser.Perms.CreateReply {
|
|
||||||
w.Write(topic_91)
|
|
||||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
|
|
||||||
w.Write(topic_92)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Write(topic_91)
|
||||||
|
if tmpl_topic_vars.CurrentUser.Perms.CreateReply {
|
||||||
|
w.Write(topic_92)
|
||||||
|
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
|
||||||
w.Write(topic_93)
|
w.Write(topic_93)
|
||||||
|
}
|
||||||
|
w.Write(topic_94)
|
||||||
w.Write(footer_0)
|
w.Write(footer_0)
|
||||||
if len(tmpl_topic_vars.Header.Themes) != 0 {
|
if len(tmpl_topic_vars.Header.Themes) != 0 {
|
||||||
for _, item := range tmpl_topic_vars.Header.Themes {
|
for _, item := range tmpl_topic_vars.Header.Themes {
|
||||||
|
|
|
@ -126,7 +126,7 @@ w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.Level)))
|
||||||
w.Write(topic_alt_24)
|
w.Write(topic_alt_24)
|
||||||
}
|
}
|
||||||
w.Write(topic_alt_25)
|
w.Write(topic_alt_25)
|
||||||
w.Write([]byte(tmpl_topic_alt_vars.Topic.Content))
|
w.Write([]byte(tmpl_topic_alt_vars.Topic.ContentHTML))
|
||||||
w.Write(topic_alt_26)
|
w.Write(topic_alt_26)
|
||||||
w.Write([]byte(tmpl_topic_alt_vars.Topic.Content))
|
w.Write([]byte(tmpl_topic_alt_vars.Topic.Content))
|
||||||
w.Write(topic_alt_27)
|
w.Write(topic_alt_27)
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<div class="rowitem"><a>Forums</a></div>
|
<div class="rowitem"><a>Forums</a></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rowblock">
|
<div class="rowblock">
|
||||||
{{range .ItemList}}<div class="rowitem {{if (.Desc) or (.LastTopicTime)}}datarow{{end}}">
|
{{range .ItemList}}<div class="rowitem {{if (.Desc) or (.LastTopic.Title)}}datarow{{end}}">
|
||||||
{{if .Desc}}<span style="float: left;">
|
{{if .Desc}}<span style="float: left;">
|
||||||
<a href="{{.Link}}" style="">{{.Name}}</a>
|
<a href="{{.Link}}" style="">{{.Name}}</a>
|
||||||
<br /><span class="rowsmall">{{.Desc}}</span>
|
<br /><span class="rowsmall">{{.Desc}}</span>
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
</span>{{end}}
|
</span>{{end}}
|
||||||
|
|
||||||
<span style="float: right;">
|
<span style="float: right;">
|
||||||
<a href="{{.LastTopicLink}}" style="float: right;font-size: 14px;">{{.LastTopic}}</a>
|
<a href="{{.LastTopic.Link}}" style="float: right;font-size: 14px;">{{if .LastTopic.Title}}{{.LastTopic.Title}}{{else}}None{{end}}</a>
|
||||||
{{if .LastTopicTime}}<br /><span class="rowsmall">{{.LastTopicTime}}</span>{{end}}
|
{{if .LastTopicTime}}<br /><span class="rowsmall">{{.LastTopicTime}}</span>{{end}}
|
||||||
</span>
|
</span>
|
||||||
<div style="clear: both;"></div>
|
<div style="clear: both;"></div>
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
<article class="rowblock post_container top_post">
|
<article class="rowblock post_container top_post">
|
||||||
<div class="rowitem passive editable_parent post_item {{.Topic.ClassName}}" style="{{if .Topic.Avatar}}background-image:url({{.Topic.Avatar}}), url(/static/post-avatar-bg.jpg);background-position: 0px {{if le .Topic.ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;{{end}}">
|
<div class="rowitem passive editable_parent post_item {{.Topic.ClassName}}" style="{{if .Topic.Avatar}}background-image:url({{.Topic.Avatar}}), url(/static/post-avatar-bg.jpg);background-position: 0px {{if le .Topic.ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;{{end}}">
|
||||||
<p class="hide_on_edit topic_content user_content" style="margin:0;padding:0;">{{.Topic.Content}}</p>
|
<p class="hide_on_edit topic_content user_content" style="margin:0;padding:0;">{{.Topic.ContentHTML}}</p>
|
||||||
<textarea name="topic_content" class="show_on_edit topic_content_input">{{.Topic.Content}}</textarea>
|
<textarea name="topic_content" class="show_on_edit topic_content_input">{{.Topic.Content}}</textarea>
|
||||||
|
|
||||||
<span class="controls">
|
<span class="controls">
|
||||||
|
@ -56,6 +56,7 @@
|
||||||
</article>
|
</article>
|
||||||
{{else}}
|
{{else}}
|
||||||
<article class="rowitem passive deletable_block editable_parent post_item {{.ClassName}}" style="{{if .Avatar}}background-image:url({{.Avatar}}), url(/static/post-avatar-bg.jpg);background-position: 0px {{if le .ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;{{end}}">
|
<article class="rowitem passive deletable_block editable_parent post_item {{.ClassName}}" style="{{if .Avatar}}background-image:url({{.Avatar}}), url(/static/post-avatar-bg.jpg);background-position: 0px {{if le .ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;{{end}}">
|
||||||
|
{{/** TODO: We might end up with <br>s in the inline editor, fix this **/}}
|
||||||
<p class="editable_block user_content" style="margin:0;padding:0;">{{.ContentHtml}}</p>
|
<p class="editable_block user_content" style="margin:0;padding:0;">{{.ContentHtml}}</p>
|
||||||
|
|
||||||
<span class="controls">
|
<span class="controls">
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
{{if .Topic.Tag}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">{{.Topic.Tag}}</div><div class="tag_post"></div></div>{{else}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">Level {{.Topic.Level}}</div><div class="tag_post"></div></div>{{end}}
|
{{if .Topic.Tag}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">{{.Topic.Tag}}</div><div class="tag_post"></div></div>{{else}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">Level {{.Topic.Level}}</div><div class="tag_post"></div></div>{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="content_container">
|
<div class="content_container">
|
||||||
<div class="hide_on_edit topic_content user_content">{{.Topic.Content}}</div>
|
<div class="hide_on_edit topic_content user_content">{{.Topic.ContentHTML}}</div>
|
||||||
<textarea name="topic_content" class="show_on_edit topic_content_input">{{.Topic.Content}}</textarea>
|
<textarea name="topic_content" class="show_on_edit topic_content_input">{{.Topic.Content}}</textarea>
|
||||||
<div class="button_container">
|
<div class="button_container">
|
||||||
{{if .CurrentUser.Loggedin}}
|
{{if .CurrentUser.Loggedin}}
|
||||||
|
@ -58,6 +58,7 @@
|
||||||
<span class="action_icon" style="font-size: 18px;padding-right: 5px;">{{.ActionIcon}}</span>
|
<span class="action_icon" style="font-size: 18px;padding-right: 5px;">{{.ActionIcon}}</span>
|
||||||
<span>{{.ActionType}}</span>
|
<span>{{.ActionType}}</span>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
{{/** TODO: We might end up with <br>s in the inline editor, fix this **/}}
|
||||||
<div class="editable_block user_content">{{.ContentHtml}}</div>
|
<div class="editable_block user_content">{{.ContentHtml}}</div>
|
||||||
<div class="button_container">
|
<div class="button_container">
|
||||||
{{if $.CurrentUser.Loggedin}}
|
{{if $.CurrentUser.Loggedin}}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="rowblock rowhead">
|
<div class="rowblock rowhead">
|
||||||
<div class="rowitem"><h1>Topic List</h1></div>
|
<div class="rowitem"><h1>Topic List</h1></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="topic_list" class="rowblock topic_list">
|
<div id="topic_list" class="rowblock topic_list" aria-label="The main topic list">
|
||||||
{{range .ItemList}}<div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}" style="{{if .Creator.Avatar}}background-image: url({{.Creator.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}">
|
{{range .ItemList}}<div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}" style="{{if .Creator.Avatar}}background-image: url({{.Creator.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}">
|
||||||
<span class="topic_inner_right rowsmall" style="float: right;">
|
<span class="topic_inner_right rowsmall" style="float: right;">
|
||||||
<span class="replyCount">{{.PostCount}} replies</span><br />
|
<span class="replyCount">{{.PostCount}} replies</span><br />
|
||||||
|
|
|
@ -7,6 +7,11 @@ body {
|
||||||
background-color: #222222;
|
background-color: #222222;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
p::selection, span::selection, a::selection {
|
||||||
|
background-color: hsl(0,0%,75%);
|
||||||
|
color: hsl(0,0%,20%);
|
||||||
|
font-weight: 100;
|
||||||
|
}
|
||||||
|
|
||||||
#back {
|
#back {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
@ -133,7 +138,7 @@ a {
|
||||||
}
|
}
|
||||||
|
|
||||||
.rowblock:not(.opthead):not(.colstack_head):not(.rowhead) .rowitem {
|
.rowblock:not(.opthead):not(.colstack_head):not(.rowhead) .rowitem {
|
||||||
font-size: 15px;
|
font-size: 15px; /*16px*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.rowblock:last-child, .colstack_item:last-child {
|
.rowblock:last-child, .colstack_item:last-child {
|
||||||
|
@ -479,7 +484,7 @@ input, select, textarea {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Forum View */
|
/* Forum View */
|
||||||
.rowhead, .opthead, .colstack_head {
|
.rowhead, .opthead, .colstack_head, .rowhead .rowitem {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
@ -797,6 +802,9 @@ input, select, textarea {
|
||||||
.topic_list .topic_right {
|
.topic_list .topic_right {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
#poweredBy span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(max-width: 470px) {
|
@media(max-width: 470px) {
|
||||||
|
|
39
topic.go
39
topic.go
|
@ -7,8 +7,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//import "fmt"
|
//import "fmt"
|
||||||
import "strconv"
|
import (
|
||||||
import "html/template"
|
"html"
|
||||||
|
"html/template"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ? - Add a TopicMeta struct for *Forums?
|
||||||
|
|
||||||
type Topic struct {
|
type Topic struct {
|
||||||
ID int
|
ID int
|
||||||
|
@ -54,6 +59,7 @@ type TopicUser struct {
|
||||||
Group int
|
Group int
|
||||||
Avatar string
|
Avatar string
|
||||||
ContentLines int
|
ContentLines int
|
||||||
|
ContentHTML string
|
||||||
Tag string
|
Tag string
|
||||||
URL string
|
URL string
|
||||||
URLPrefix string
|
URLPrefix string
|
||||||
|
@ -138,6 +144,18 @@ func (topic *Topic) RemoveLike(uid int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (topic *Topic) Update(name string, content string) error {
|
||||||
|
content = preparseMessage(content)
|
||||||
|
parsed_content := parseMessage(html.EscapeString(content))
|
||||||
|
_, err := editTopicStmt.Exec(name, content, parsed_content, topic.ID)
|
||||||
|
|
||||||
|
tcache, ok := topics.(TopicCache)
|
||||||
|
if ok {
|
||||||
|
tcache.CacheRemove(topic.ID)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (topic *Topic) CreateActionReply(action string, ipaddress string, user User) (err error) {
|
func (topic *Topic) CreateActionReply(action string, ipaddress string, user User) (err error) {
|
||||||
_, err = createActionReplyStmt.Exec(topic.ID, action, ipaddress, user.ID)
|
_, err = createActionReplyStmt.Exec(topic.ID, action, ipaddress, user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -152,8 +170,12 @@ func (topic *Topic) CreateActionReply(action string, ipaddress string, user User
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (topic *Topic) Copy() Topic {
|
||||||
|
return *topic
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Refactor the caller to take a Topic and a User rather than a combined TopicUser
|
// TODO: Refactor the caller to take a Topic and a User rather than a combined TopicUser
|
||||||
func getTopicuser(tid int) (TopicUser, error) {
|
func getTopicUser(tid int) (TopicUser, error) {
|
||||||
tcache, tok := topics.(TopicCache)
|
tcache, tok := topics.(TopicCache)
|
||||||
ucache, uok := users.(UserCache)
|
ucache, uok := users.(UserCache)
|
||||||
if tok && uok {
|
if tok && uok {
|
||||||
|
@ -165,7 +187,7 @@ func getTopicuser(tid int) (TopicUser, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We might be better off just passing seperate topic and user structs to the caller?
|
// We might be better off just passing seperate topic and user structs to the caller?
|
||||||
return copyTopicToTopicuser(topic, user), nil
|
return copyTopicToTopicUser(topic, user), nil
|
||||||
} else if ucache.GetLength() < ucache.GetCapacity() {
|
} else if ucache.GetLength() < ucache.GetCapacity() {
|
||||||
topic, err = topics.Get(tid)
|
topic, err = topics.Get(tid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -175,7 +197,7 @@ func getTopicuser(tid int) (TopicUser, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return TopicUser{ID: tid}, err
|
return TopicUser{ID: tid}, err
|
||||||
}
|
}
|
||||||
return copyTopicToTopicuser(topic, user), nil
|
return copyTopicToTopicUser(topic, user), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +215,7 @@ func getTopicuser(tid int) (TopicUser, error) {
|
||||||
return tu, err
|
return tu, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyTopicToTopicuser(topic *Topic, user *User) (tu TopicUser) {
|
func copyTopicToTopicUser(topic *Topic, user *User) (tu TopicUser) {
|
||||||
tu.UserLink = user.Link
|
tu.UserLink = user.Link
|
||||||
tu.CreatedByName = user.Name
|
tu.CreatedByName = user.Name
|
||||||
tu.Group = user.Group
|
tu.Group = user.Group
|
||||||
|
@ -220,6 +242,11 @@ func copyTopicToTopicuser(topic *Topic, user *User) (tu TopicUser) {
|
||||||
return tu
|
return tu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For use in tests and for generating blank topics for forums which don't have a last poster
|
||||||
|
func getDummyTopic() *Topic {
|
||||||
|
return &Topic{ID: 0, Title: ""}
|
||||||
|
}
|
||||||
|
|
||||||
func getTopicByReply(rid int) (*Topic, error) {
|
func getTopicByReply(rid int) (*Topic, error) {
|
||||||
topic := Topic{ID: 0}
|
topic := Topic{ID: 0}
|
||||||
err := getTopicByReplyStmt.QueryRow(rid).Scan(&topic.ID, &topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
|
err := getTopicByReplyStmt.QueryRow(rid).Scan(&topic.ID, &topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
|
||||||
|
|
|
@ -61,7 +61,7 @@ type MemoryTopicStore struct {
|
||||||
|
|
||||||
// NewMemoryTopicStore gives you a new instance of MemoryTopicStore
|
// NewMemoryTopicStore gives you a new instance of MemoryTopicStore
|
||||||
func NewMemoryTopicStore(capacity int) *MemoryTopicStore {
|
func NewMemoryTopicStore(capacity int) *MemoryTopicStore {
|
||||||
getStmt, err := qgen.Builder.SimpleSelect("topics", "title, content, createdBy, createdAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data", "tid = ?", "", "")
|
getStmt, err := qgen.Builder.SimpleSelect("topics", "title, content, createdBy, createdAt, lastReplyAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data", "tid = ?", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ func (mts *MemoryTopicStore) Get(id int) (*Topic, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
topic = &Topic{ID: id}
|
topic = &Topic{ID: id}
|
||||||
err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
|
err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
|
topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
|
||||||
_ = mts.CacheAdd(topic)
|
_ = mts.CacheAdd(topic)
|
||||||
|
@ -125,14 +125,14 @@ func (mts *MemoryTopicStore) Get(id int) (*Topic, error) {
|
||||||
// BypassGet will always bypass the cache and pull the topic directly from the database
|
// BypassGet will always bypass the cache and pull the topic directly from the database
|
||||||
func (mts *MemoryTopicStore) BypassGet(id int) (*Topic, error) {
|
func (mts *MemoryTopicStore) BypassGet(id int) (*Topic, error) {
|
||||||
topic := &Topic{ID: id}
|
topic := &Topic{ID: id}
|
||||||
err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
|
err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
|
||||||
topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
|
topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
|
||||||
return topic, err
|
return topic, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mts *MemoryTopicStore) Reload(id int) error {
|
func (mts *MemoryTopicStore) Reload(id int) error {
|
||||||
topic := &Topic{ID: id}
|
topic := &Topic{ID: id}
|
||||||
err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
|
err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
|
topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
|
||||||
_ = mts.CacheSet(topic)
|
_ = mts.CacheSet(topic)
|
||||||
|
@ -160,7 +160,7 @@ func (mts *MemoryTopicStore) Delete(id int) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = fstore.DecrementTopicCount(topic.ParentID)
|
err = fstore.RemoveTopic(topic.ParentID)
|
||||||
if err != nil && err != ErrNoRows {
|
if err != nil && err != ErrNoRows {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -271,7 +271,7 @@ type SQLTopicStore struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSQLTopicStore() *SQLTopicStore {
|
func NewSQLTopicStore() *SQLTopicStore {
|
||||||
getStmt, err := qgen.Builder.SimpleSelect("topics", "title, content, createdBy, createdAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data", "tid = ?", "", "")
|
getStmt, err := qgen.Builder.SimpleSelect("topics", "title, content, createdBy, createdAt, lastReplyAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data", "tid = ?", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -297,7 +297,7 @@ func NewSQLTopicStore() *SQLTopicStore {
|
||||||
|
|
||||||
func (sts *SQLTopicStore) Get(id int) (*Topic, error) {
|
func (sts *SQLTopicStore) Get(id int) (*Topic, error) {
|
||||||
topic := Topic{ID: id}
|
topic := Topic{ID: id}
|
||||||
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
|
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
|
||||||
topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
|
topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
|
||||||
return &topic, err
|
return &topic, err
|
||||||
}
|
}
|
||||||
|
@ -305,7 +305,7 @@ func (sts *SQLTopicStore) Get(id int) (*Topic, error) {
|
||||||
// BypassGet is an alias of Get(), as we don't have a cache for SQLTopicStore
|
// BypassGet is an alias of Get(), as we don't have a cache for SQLTopicStore
|
||||||
func (sts *SQLTopicStore) BypassGet(id int) (*Topic, error) {
|
func (sts *SQLTopicStore) BypassGet(id int) (*Topic, error) {
|
||||||
topic := &Topic{ID: id}
|
topic := &Topic{ID: id}
|
||||||
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
|
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
|
||||||
topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
|
topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
|
||||||
return topic, err
|
return topic, err
|
||||||
}
|
}
|
||||||
|
@ -332,7 +332,7 @@ func (sts *SQLTopicStore) Delete(id int) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = fstore.DecrementTopicCount(topic.ParentID)
|
err = fstore.RemoveTopic(topic.ParentID)
|
||||||
if err != nil && err != ErrNoRows {
|
if err != nil && err != ErrNoRows {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
9
user.go
9
user.go
|
@ -201,6 +201,10 @@ func (user *User) decreasePostStats(wcount int, topic bool) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (user *User) Copy() User {
|
||||||
|
return *user
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Write unit tests for this
|
// TODO: Write unit tests for this
|
||||||
func (user *User) initPerms() {
|
func (user *User) initPerms() {
|
||||||
if user.TempGroup != 0 {
|
if user.TempGroup != 0 {
|
||||||
|
@ -290,6 +294,11 @@ func wordsToScore(wcount int, topic bool) (score int) {
|
||||||
return score
|
return score
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For use in tests and to help generate dummy users for forums which don't have last posters
|
||||||
|
func getDummyUser() *User {
|
||||||
|
return &User{ID: 0, Name: ""}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Write unit tests for this
|
// TODO: Write unit tests for this
|
||||||
func buildProfileURL(slug string, uid int) string {
|
func buildProfileURL(slug string, uid int) string {
|
||||||
if slug == "" {
|
if slug == "" {
|
||||||
|
|
Loading…
Reference in New Issue