Added the GroupStore.

Renamed the *Store methods.
Added more functionality to some of the DataStores.
Added DataCache interfaces in addition to the DataStores to help curb their unrelenting growth and confusing APIs.
Fixed a crash bug in the ForumStore getters.
Fixed three tests.
Added more tests.
Temporary Group permissions should now be applied properly.
Improved the Tempra Conflux theme.
Moved the topic deletion logic into the TopicStore.
Tweaked the permission checks on the member routes to make them more sensible.
This commit is contained in:
Azareal 2017-09-15 23:20:01 +01:00
parent b02897e6e1
commit 2557eb935b
33 changed files with 1106 additions and 703 deletions

View File

@ -31,7 +31,7 @@ Other modern features like alerts, likes, advanced dashboard with live stats (CP
# Dependencies # Dependencies
Go 1.8 - You will need to install this. Pick the .msi, if you want everything sorted out for you rather than having to go around updating the environment settings. https://golang.org/doc/install Go 1.9 - You will need to install this. Pick the .msi, if you want everything sorted out for you rather than having to go around updating the environment settings. https://golang.org/doc/install
MySQL Database - You will need to setup a MySQL Database somewhere. A MariaDB Database works equally well and is much faster than MySQL. You could use something like WNMP / XAMPP which have a little PHP script called PhpMyAdmin for managing MySQL databases or you could install MariaDB directly. MySQL Database - You will need to setup a MySQL Database somewhere. A MariaDB Database works equally well and is much faster than MySQL. You could use something like WNMP / XAMPP which have a little PHP script called PhpMyAdmin for managing MySQL databases or you could install MariaDB directly.

View File

@ -29,13 +29,13 @@ import "errors"
func buildAlert(asid int, event string, elementType string, actorID int, targetUserID int, elementID int, user User /* The current user */) (string, error) { func buildAlert(asid int, event string, elementType string, actorID int, targetUserID int, elementID int, user User /* The current user */) (string, error) {
var targetUser *User var targetUser *User
actor, err := users.CascadeGet(actorID) actor, err := users.Get(actorID)
if err != nil { if err != nil {
return "", errors.New("Unable to find the actor") return "", errors.New("Unable to find the actor")
} }
/*if elementType != "forum" { /*if elementType != "forum" {
targetUser, err = users.CascadeGet(targetUser_id) targetUser, err = users.Get(targetUser_id)
if err != nil { if err != nil {
LocalErrorJS("Unable to find the target user",w,r) LocalErrorJS("Unable to find the target user",w,r)
return return
@ -52,7 +52,7 @@ func buildAlert(asid int, event string, elementType string, actorID int, targetU
case "forum": case "forum":
if event == "reply" { if event == "reply" {
act = "created a new topic" act = "created a new topic"
topic, err := topics.CascadeGet(elementID) topic, err := topics.Get(elementID)
if err != nil { if err != nil {
return "", errors.New("Unable to find the linked topic") return "", errors.New("Unable to find the linked topic")
} }
@ -64,7 +64,7 @@ func buildAlert(asid int, event string, elementType string, actorID int, targetU
act = "did something in a forum" act = "did something in a forum"
} }
case "topic": case "topic":
topic, err := topics.CascadeGet(elementID) topic, err := topics.Get(elementID)
if err != nil { if err != nil {
return "", errors.New("Unable to find the linked topic") return "", errors.New("Unable to find the linked topic")
} }
@ -75,7 +75,7 @@ func buildAlert(asid int, event string, elementType string, actorID int, targetU
postAct = " your topic" postAct = " your topic"
} }
case "user": case "user":
targetUser, err = users.CascadeGet(elementID) targetUser, err = users.Get(elementID)
if err != nil { if err != nil {
return "", errors.New("Unable to find the target user") return "", errors.New("Unable to find the target user")
} }

View File

@ -93,7 +93,7 @@ func (auth *DefaultAuth) ForceLogout(uid int) error {
} }
// Flush the user out of the cache and reload // Flush the user out of the cache and reload
err = users.Load(uid) err = users.Reload(uid)
if err != nil { if err != nil {
return errors.New("Your account no longer exists.") return errors.New("Your account no longer exists.")
} }
@ -141,7 +141,7 @@ func (auth *DefaultAuth) SessionCheck(w http.ResponseWriter, r *http.Request) (u
} }
// Is this session valid..? // Is this session valid..?
user, err = users.CascadeGet(uid) user, err = users.Get(uid)
if err == ErrNoRows { if err == ErrNoRows {
return &guestUser, false return &guestUser, false
} else if err != nil { } else if err != nil {
@ -168,6 +168,6 @@ func (auth *DefaultAuth) CreateSession(uid int) (session string, err error) {
} }
// Reload the user data // Reload the user data
_ = users.Load(uid) _ = users.Reload(uid)
return session, nil return session, nil
} }

View File

@ -18,14 +18,18 @@ var ErrStoreCapacityOverflow = errors.New("This datastore has reached it's maxim
type DataStore interface { type DataStore interface {
Load(id int) error Load(id int) error
Get(id int) (interface{}, error) Get(id int) (interface{}, error)
GetUnsafe(id int) (interface{}, error)
CascadeGet(id int) (interface{}, error)
BypassGet(id int) (interface{}, error) BypassGet(id int) (interface{}, error)
Set(item interface{}) error //GetGlobalCount()
Add(item interface{}) error }
AddUnsafe(item interface{}) error
Remove(id int) error type DataCache interface {
RemoveUnsafe(id int) error CacheGet(id int) (interface{}, error)
CacheGetUnsafe(id int) (interface{}, error)
CacheSet(item interface{}) error
CacheAdd(item interface{}) error
CacheAddUnsafe(item interface{}) error
CacheRemove(id int) error
CacheRemoveUnsafe(id int) error
GetLength() int GetLength() int
GetCapacity() int GetCapacity() int
} }

View File

@ -1,7 +1,7 @@
package main package main
import "log" import "log"
import "encoding/json"
import "database/sql" import "database/sql"
var db *sql.DB var db *sql.DB
@ -19,57 +19,11 @@ func initDatabase() (err error) {
} }
log.Print("Loading the usergroups.") log.Print("Loading the usergroups.")
groups = append(groups, Group{ID: 0, Name: "System"}) gstore = NewMemoryGroupStore()
err = gstore.LoadGroups()
rows, err := get_groups_stmt.Query()
if err != nil { if err != nil {
return err return err
} }
defer rows.Close()
i := 1
for ; rows.Next(); i++ {
group := Group{ID: 0}
err := rows.Scan(&group.ID, &group.Name, &group.PermissionsText, &group.PluginPermsText, &group.IsMod, &group.IsAdmin, &group.IsBanned, &group.Tag)
if err != nil {
return err
}
// Ugh, you really shouldn't physically delete these items, it makes a big mess of things
if group.ID != i {
log.Print("Stop physically deleting groups. You are messing up the IDs. Use the Group Manager or delete_group() instead x.x")
fillGroupIDGap(i, group.ID)
}
err = json.Unmarshal(group.PermissionsText, &group.Perms)
if err != nil {
return err
}
if dev.DebugMode {
log.Print(group.Name + ": ")
log.Printf("%+v\n", group.Perms)
}
err = json.Unmarshal(group.PluginPermsText, &group.PluginPerms)
if err != nil {
return err
}
if dev.DebugMode {
log.Print(group.Name + ": ")
log.Printf("%+v\n", group.PluginPerms)
}
//group.Perms.ExtData = make(map[string]bool)
groups = append(groups, group)
}
err = rows.Err()
if err != nil {
return err
}
groupCapCount = i
log.Print("Binding the Not Loggedin Group")
GuestPerms = groups[6].Perms
log.Print("Loading the forums.") log.Print("Loading the forums.")
fstore = NewMemoryForumStore() fstore = NewMemoryForumStore()

View File

@ -17,7 +17,7 @@ import (
var forumUpdateMutex sync.Mutex var forumUpdateMutex sync.Mutex
var forumCreateMutex sync.Mutex var forumCreateMutex sync.Mutex
var forumPerms map[int]map[int]ForumPerms // [gid][fid]Perms var forumPerms map[int]map[int]ForumPerms // [gid][fid]Perms // TODO: Add an abstraction around this and make it more thread-safe
var fstore ForumStore var fstore ForumStore
// ForumStore is an interface for accessing the forums and the metadata stored on them // ForumStore is an interface for accessing the forums and the metadata stored on them
@ -25,15 +25,11 @@ 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)
CascadeGet(id int) (*Forum, error) GetCopy(id int) (Forum, error)
CascadeGetCopy(id int) (Forum, error)
BypassGet(id int) (*Forum, error) BypassGet(id int) (*Forum, error)
Load(id int) error Reload(id int) error // ? - Should we move this to TopicCache? Might require us to do a lot more casting in Gosora though...
Set(forum *Forum) error
//Update(Forum) error //Update(Forum) error
//CascadeUpdate(Forum) error Delete(id int) error
Delete(id int)
CascadeDelete(id int) error
IncrementTopicCount(id int) error IncrementTopicCount(id int) error
DecrementTopicCount(id int) error DecrementTopicCount(id int) error
UpdateLastTopic(topicName string, tid int, username string, uid int, time string, fid int) error UpdateLastTopic(topicName string, tid int, username string, uid int, time string, fid int) error
@ -44,11 +40,17 @@ type ForumStore interface {
GetAllVisibleIDs() ([]int, error) GetAllVisibleIDs() ([]int, error)
//GetChildren(parentID int, parentType string) ([]*Forum,error) //GetChildren(parentID int, parentType string) ([]*Forum,error)
//GetFirstChild(parentID int, parentType string) (*Forum,error) //GetFirstChild(parentID int, parentType string) (*Forum,error)
CreateForum(forumName string, forumDesc string, active bool, preset string) (int, error) Create(forumName string, forumDesc string, active bool, preset string) (int, error)
GetGlobalCount() int GetGlobalCount() int
} }
type ForumCache interface {
CacheGet(id int) (*Forum, error)
CacheSet(forum *Forum) error
CacheDelete(id 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
type MemoryForumStore struct { type MemoryForumStore struct {
forums sync.Map // map[int]*Forum forums sync.Map // map[int]*Forum
@ -158,39 +160,38 @@ func (mfs *MemoryForumStore) DirtyGet(id int) *Forum {
return forum return forum
} }
func (mfs *MemoryForumStore) Get(id int) (*Forum, error) { func (mfs *MemoryForumStore) CacheGet(id int) (*Forum, error) {
fint, ok := mfs.forums.Load(id) fint, ok := mfs.forums.Load(id)
forum := fint.(*Forum) if !ok || fint.(*Forum).Name == "" {
if !ok || forum.Name == "" {
return nil, ErrNoRows return nil, ErrNoRows
} }
return forum, nil return fint.(*Forum), nil
} }
func (mfs *MemoryForumStore) CascadeGet(id int) (*Forum, error) { func (mfs *MemoryForumStore) Get(id int) (*Forum, error) {
fint, ok := mfs.forums.Load(id) fint, ok := mfs.forums.Load(id)
forum := fint.(*Forum) if !ok || fint.(*Forum).Name == "" {
if !ok || 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) 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.Link = buildForumURL(nameToSlug(forum.Name), forum.ID)
forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID) forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID)
return forum, err return forum, err
} }
return forum, nil return fint.(*Forum), nil
} }
func (mfs *MemoryForumStore) CascadeGetCopy(id int) (Forum, error) { func (mfs *MemoryForumStore) GetCopy(id int) (Forum, error) {
fint, ok := mfs.forums.Load(id) fint, ok := mfs.forums.Load(id)
forum := fint.(*Forum) if !ok || fint.(*Forum).Name == "" {
if !ok || 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) 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.Link = buildForumURL(nameToSlug(forum.Name), forum.ID)
forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID) forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID)
return *forum, err return forum, err
} }
return *forum, nil return *fint.(*Forum), nil
} }
func (mfs *MemoryForumStore) BypassGet(id int) (*Forum, error) { func (mfs *MemoryForumStore) BypassGet(id int) (*Forum, error) {
@ -202,7 +203,7 @@ func (mfs *MemoryForumStore) BypassGet(id int) (*Forum, error) {
return &forum, err return &forum, err
} }
func (mfs *MemoryForumStore) Load(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 {
@ -211,11 +212,11 @@ func (mfs *MemoryForumStore) Load(id int) 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) forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID)
mfs.Set(&forum) mfs.CacheSet(&forum)
return nil return nil
} }
func (mfs *MemoryForumStore) Set(forum *Forum) error { func (mfs *MemoryForumStore) CacheSet(forum *Forum) error {
if !mfs.Exists(forum.ID) { if !mfs.Exists(forum.ID) {
return ErrNoRows return ErrNoRows
} }
@ -261,30 +262,31 @@ func (mfs *MemoryForumStore) GetFirstChild(parentID int, parentType string) (*Fo
return nil, nil return nil, nil
}*/ }*/
// TODO: Add a query for this rather than hitting cache
func (mfs *MemoryForumStore) Exists(id int) bool { func (mfs *MemoryForumStore) Exists(id int) bool {
forum, ok := mfs.forums.Load(id) forum, ok := mfs.forums.Load(id)
return ok && forum.(*Forum).Name != "" return ok && forum.(*Forum).Name != ""
} }
// TODO: Batch deletions with name blanking? Is this necessary? // TODO: Batch deletions with name blanking? Is this necessary?
func (mfs *MemoryForumStore) Delete(id int) { func (mfs *MemoryForumStore) CacheDelete(id int) {
mfs.forums.Delete(id) mfs.forums.Delete(id)
mfs.rebuildView() mfs.rebuildView()
} }
func (mfs *MemoryForumStore) CascadeDelete(id int) error { func (mfs *MemoryForumStore) Delete(id int) error {
forumUpdateMutex.Lock() forumUpdateMutex.Lock()
defer forumUpdateMutex.Unlock() defer forumUpdateMutex.Unlock()
_, err := mfs.delete.Exec(id) _, err := mfs.delete.Exec(id)
if err != nil { if err != nil {
return err return err
} }
mfs.Delete(id) mfs.CacheDelete(id)
return nil return nil
} }
func (mfs *MemoryForumStore) IncrementTopicCount(id int) error { func (mfs *MemoryForumStore) IncrementTopicCount(id int) error {
forum, err := mfs.CascadeGet(id) forum, err := mfs.Get(id)
if err != nil { if err != nil {
return err return err
} }
@ -297,7 +299,7 @@ func (mfs *MemoryForumStore) IncrementTopicCount(id int) error {
} }
func (mfs *MemoryForumStore) DecrementTopicCount(id int) error { func (mfs *MemoryForumStore) DecrementTopicCount(id int) error {
forum, err := mfs.CascadeGet(id) forum, err := mfs.Get(id)
if err != nil { if err != nil {
return err return err
} }
@ -311,7 +313,7 @@ func (mfs *MemoryForumStore) DecrementTopicCount(id int) error {
// 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
func (mfs *MemoryForumStore) UpdateLastTopic(topicName string, tid int, username string, uid int, time string, fid int) error { func (mfs *MemoryForumStore) UpdateLastTopic(topicName string, tid int, username string, uid int, time string, fid int) error {
forum, err := mfs.CascadeGet(fid) forum, err := mfs.Get(fid)
if err != nil { if err != nil {
return err return err
} }
@ -330,7 +332,7 @@ func (mfs *MemoryForumStore) UpdateLastTopic(topicName string, tid int, username
return nil return nil
} }
func (mfs *MemoryForumStore) CreateForum(forumName string, forumDesc string, active bool, preset string) (int, error) { func (mfs *MemoryForumStore) Create(forumName string, forumDesc string, active bool, preset string) (int, error) {
forumCreateMutex.Lock() forumCreateMutex.Lock()
res, err := create_forum_stmt.Exec(forumName, forumDesc, active, preset) res, err := create_forum_stmt.Exec(forumName, forumDesc, active, preset)
if err != nil { if err != nil {

View File

@ -100,7 +100,7 @@ func BenchmarkTopicAdminRouteParallel(b *testing.B) {
} }
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
admin, err := users.CascadeGet(1) admin, err := users.Get(1)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@ -157,7 +157,7 @@ func BenchmarkForumsAdminRouteParallel(b *testing.B) {
} }
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
admin, err := users.CascadeGet(1) admin, err := users.Get(1)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -188,7 +188,7 @@ func BenchmarkForumsAdminRouteParallelProf(b *testing.B) {
} }
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
admin, err := users.CascadeGet(1) admin, err := users.Get(1)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -237,7 +237,7 @@ func BenchmarkForumsGuestRouteParallel(b *testing.B) {
/*func BenchmarkRoutesSerial(b *testing.B) { /*func BenchmarkRoutesSerial(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
admin, err := users.CascadeGet(1) admin, err := users.Get(1)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -1125,7 +1125,7 @@ func TestStaticRoute(t *testing.T) {
init_plugins() init_plugins()
} }
admin, err := users.CascadeGet(1) admin, err := users.Get(1)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -1181,7 +1181,7 @@ func TestForumsAdminRoute(t *testing.T) {
init_plugins() init_plugins()
} }
admin, err := users.CascadeGet(1) admin, err := users.Get(1)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1231,7 +1231,7 @@ func TestForumsGuestRoute(t *testing.T) {
init_plugins() init_plugins()
} }
admin, err := users.CascadeGet(1) admin, err := users.Get(1)
if err != nil { if err != nil {
panic(err) panic(err)
} }

104
group.go
View File

@ -1,9 +1,6 @@
package main package main
import "sync" var blankGroup = Group{ID: 0, Name: ""}
import "encoding/json"
var groupUpdateMutex sync.Mutex
type GroupAdmin struct { type GroupAdmin struct {
ID int ID int
@ -28,102 +25,3 @@ 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
} }
var groupCreateMutex sync.Mutex
func createGroup(groupName string, tag string, isAdmin bool, isMod bool, isBanned bool) (int, error) {
var gid int
err := group_entry_exists_stmt.QueryRow().Scan(&gid)
if err != nil && err != ErrNoRows {
return 0, err
}
if err != ErrNoRows {
groupUpdateMutex.Lock()
_, err = update_group_rank_stmt.Exec(isAdmin, isMod, isBanned, gid)
if err != nil {
return gid, err
}
_, err = update_group_stmt.Exec(groupName, tag, gid)
if err != nil {
return gid, err
}
groups[gid].Name = groupName
groups[gid].Tag = tag
groups[gid].IsBanned = isBanned
groups[gid].IsMod = isMod
groups[gid].IsAdmin = isAdmin
groupUpdateMutex.Unlock()
return gid, nil
}
groupCreateMutex.Lock()
var permstr = "{}"
res, err := create_group_stmt.Exec(groupName, tag, isAdmin, isMod, isBanned, permstr)
if err != nil {
return 0, err
}
gid64, err := res.LastInsertId()
if err != nil {
return 0, err
}
gid = int(gid64)
var perms = BlankPerms
var blankForums []ForumPerms
var blankIntList []int
var pluginPerms = make(map[string]bool)
var pluginPermsBytes = []byte("{}")
if vhooks["create_group_preappend"] != nil {
runVhook("create_group_preappend", &pluginPerms, &pluginPermsBytes)
}
groups = append(groups, Group{gid, groupName, isMod, isAdmin, isBanned, tag, perms, []byte(permstr), pluginPerms, pluginPermsBytes, blankForums, blankIntList})
groupCreateMutex.Unlock()
// Generate the forum permissions based on the presets...
fdata, err := fstore.GetAll()
if err != nil {
return 0, err
}
permUpdateMutex.Lock()
for _, forum := range fdata {
var thePreset string
if isAdmin {
thePreset = "admins"
} else if isMod {
thePreset = "staff"
} else if isBanned {
thePreset = "banned"
} else {
thePreset = "members"
}
permmap := presetToPermmap(forum.Preset)
permitem := permmap[thePreset]
permitem.Overrides = true
permstr, err := json.Marshal(permitem)
if err != nil {
return gid, err
}
perms := string(permstr)
_, err = add_forum_perms_to_group_stmt.Exec(gid, forum.ID, forum.Preset, perms)
if err != nil {
return gid, err
}
err = rebuildForumPermissions(forum.ID)
if err != nil {
return gid, err
}
}
permUpdateMutex.Unlock()
return gid, nil
}
func groupExists(gid int) bool {
return (gid <= groupCapCount) && (gid > 0) && groups[gid].Name != ""
}

View File

@ -1,3 +1,205 @@
/* Under Heavy Construction */
package main package main
// TODO: Coming Soon. Probably in the next commit or two import (
"encoding/json"
"errors"
"log"
"sync"
)
var groupCreateMutex sync.Mutex
var groupUpdateMutex sync.Mutex
var gstore GroupStore
type GroupStore interface {
LoadGroups() error
DirtyGet(id int) *Group
Get(id int) (*Group, error)
GetCopy(id int) (Group, error)
Exists(id int) bool
Create(groupName string, tag string, isAdmin bool, isMod bool, isBanned bool) (int, error)
GetAll() ([]*Group, error)
GetRange(lower int, higher int) ([]*Group, error)
}
type MemoryGroupStore struct {
groups []*Group // TODO: Use a sync.Map instead of a slice
groupCapCount int
}
func NewMemoryGroupStore() *MemoryGroupStore {
return &MemoryGroupStore{}
}
func (mgs *MemoryGroupStore) LoadGroups() error {
mgs.groups = []*Group{&Group{ID: 0, Name: "Unknown"}}
rows, err := get_groups_stmt.Query()
if err != nil {
return err
}
defer rows.Close()
i := 1
for ; rows.Next(); i++ {
group := Group{ID: 0}
err := rows.Scan(&group.ID, &group.Name, &group.PermissionsText, &group.PluginPermsText, &group.IsMod, &group.IsAdmin, &group.IsBanned, &group.Tag)
if err != nil {
return err
}
err = json.Unmarshal(group.PermissionsText, &group.Perms)
if err != nil {
return err
}
if dev.DebugMode {
log.Print(group.Name + ": ")
log.Printf("%+v\n", group.Perms)
}
err = json.Unmarshal(group.PluginPermsText, &group.PluginPerms)
if err != nil {
return err
}
if dev.DebugMode {
log.Print(group.Name + ": ")
log.Printf("%+v\n", group.PluginPerms)
}
//group.Perms.ExtData = make(map[string]bool)
mgs.groups = append(mgs.groups, &group)
}
err = rows.Err()
if err != nil {
return err
}
mgs.groupCapCount = i
if dev.DebugMode {
log.Print("Binding the Not Loggedin Group")
}
GuestPerms = mgs.groups[6].Perms
return nil
}
func (mgs *MemoryGroupStore) DirtyGet(gid int) *Group {
if !mgs.Exists(gid) {
return &blankGroup
}
return mgs.groups[gid]
}
func (mgs *MemoryGroupStore) Get(gid int) (*Group, error) {
if !mgs.Exists(gid) {
return nil, ErrNoRows
}
return mgs.groups[gid], nil
}
func (mgs *MemoryGroupStore) GetCopy(gid int) (Group, error) {
if !mgs.Exists(gid) {
return blankGroup, ErrNoRows
}
return *mgs.groups[gid], nil
}
func (mgs *MemoryGroupStore) Exists(gid int) bool {
return (gid <= mgs.groupCapCount) && (gid > -1) && mgs.groups[gid].Name != ""
}
func (mgs *MemoryGroupStore) Create(groupName string, tag string, isAdmin bool, isMod bool, isBanned bool) (int, error) {
groupCreateMutex.Lock()
defer groupCreateMutex.Unlock()
var permstr = "{}"
res, err := create_group_stmt.Exec(groupName, tag, isAdmin, isMod, isBanned, permstr)
if err != nil {
return 0, err
}
gid64, err := res.LastInsertId()
if err != nil {
return 0, err
}
var gid = int(gid64)
var perms = BlankPerms
var blankForums []ForumPerms
var blankIntList []int
var pluginPerms = make(map[string]bool)
var pluginPermsBytes = []byte("{}")
if vhooks["create_group_preappend"] != nil {
runVhook("create_group_preappend", &pluginPerms, &pluginPermsBytes)
}
mgs.groups = append(mgs.groups, &Group{gid, groupName, isMod, isAdmin, isBanned, tag, perms, []byte(permstr), pluginPerms, pluginPermsBytes, blankForums, blankIntList})
// Generate the forum permissions based on the presets...
fdata, err := fstore.GetAll()
if err != nil {
return 0, err
}
permUpdateMutex.Lock()
defer permUpdateMutex.Unlock()
for _, forum := range fdata {
var thePreset string
if isAdmin {
thePreset = "admins"
} else if isMod {
thePreset = "staff"
} else if isBanned {
thePreset = "banned"
} else {
thePreset = "members"
}
permmap := presetToPermmap(forum.Preset)
permitem := permmap[thePreset]
permitem.Overrides = true
permstr, err := json.Marshal(permitem)
if err != nil {
return gid, err
}
perms := string(permstr)
_, err = add_forum_perms_to_group_stmt.Exec(gid, forum.ID, forum.Preset, perms)
if err != nil {
return gid, err
}
err = rebuildForumPermissions(forum.ID)
if err != nil {
return gid, err
}
}
return gid, nil
}
// ! NOT CONCURRENT
func (mgs *MemoryGroupStore) GetAll() ([]*Group, error) {
return mgs.groups, nil
}
// ? - It's currently faster to use GetAll(), but we'll be dropping the guarantee that the slices are ordered soon
// ? - Set the lower and higher numbers to 0 to remove the bounds
// ? - Currently uses slicing for efficiency, so it might behave a little weirdly
func (mgs *MemoryGroupStore) GetRange(lower int, higher int) (groups []*Group, err error) {
if lower == 0 && higher == 0 {
return mgs.GetAll()
} else if lower == 0 {
if higher < 0 {
return nil, errors.New("higher may not be lower than 0")
}
if higher > len(mgs.groups) {
higher = len(mgs.groups)
}
groups = mgs.groups[:higher]
} else if higher == 0 {
if lower < 0 {
return nil, errors.New("lower may not be lower than 0")
}
groups = mgs.groups[lower:]
}
return groups, nil
}

View File

@ -38,8 +38,6 @@ var startTime time.Time
var externalSites = map[string]string{ var externalSites = map[string]string{
"YT": "https://www.youtube.com/", "YT": "https://www.youtube.com/",
} }
var groups []Group
var groupCapCount int
var staticFiles = make(map[string]SFile) var staticFiles = make(map[string]SFile)
var logWriter = io.MultiWriter(os.Stderr) var logWriter = io.MultiWriter(os.Stderr)

View File

@ -13,10 +13,9 @@ import (
"time" "time"
) )
// TODO: Replace some of these !user.Loggedin's with !user.Perms.ViewTopic
// ? - 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
// ? - Should we allow banned users to make reports? How should we handle report abuse? // ? - Should we allow banned users to make reports? How should we handle report abuse?
// TODO: Add a permission to stop certain users from using avatars // TODO: Add a permission to stop certain users from using custom avatars
// ? - Log username changes and put restrictions on this? // ? - Log username changes and put restrictions on this?
func routeTopicCreate(w http.ResponseWriter, r *http.Request, user User, sfid string) { func routeTopicCreate(w http.ResponseWriter, r *http.Request, user User, sfid string) {
@ -34,7 +33,7 @@ func routeTopicCreate(w http.ResponseWriter, r *http.Request, user User, sfid st
if !ok { if !ok {
return return
} }
if !user.Loggedin || !user.Perms.CreateTopic { if !user.Perms.ViewTopic || !user.Perms.CreateTopic {
NoPermissions(w, r, user) NoPermissions(w, r, user)
return return
} }
@ -58,7 +57,12 @@ func routeTopicCreate(w http.ResponseWriter, r *http.Request, user User, sfid st
return return
} }
} else { } else {
group := groups[user.Group] group, err := gstore.Get(user.Group)
if err != nil {
LocalError("Something weird happened behind the scenes", w, r, user)
log.Print("Group #" + strconv.Itoa(user.Group) + " doesn't exist, but it's set on User #" + strconv.Itoa(user.ID))
return
}
canSee = group.CanSee canSee = group.CanSee
} }
@ -110,7 +114,7 @@ func routeTopicCreateSubmit(w http.ResponseWriter, r *http.Request, user User) {
if !ok { if !ok {
return return
} }
if !user.Loggedin || !user.Perms.CreateTopic { if !user.Perms.ViewTopic || !user.Perms.CreateTopic {
NoPermissions(w, r, user) NoPermissions(w, r, user)
return return
} }
@ -172,7 +176,7 @@ func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
topic, err := topics.CascadeGet(tid) topic, err := topics.Get(tid)
if err == ErrNoRows { if err == ErrNoRows {
PreError("Couldn't find the parent topic", w, r) PreError("Couldn't find the parent topic", w, r)
return return
@ -186,7 +190,7 @@ func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) {
if !ok { if !ok {
return return
} }
if !user.Loggedin || !user.Perms.CreateReply { if !user.Perms.ViewTopic || !user.Perms.CreateReply {
NoPermissions(w, r, user) NoPermissions(w, r, user)
return return
} }
@ -239,7 +243,7 @@ func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) {
} }
// Reload the topic... // Reload the topic...
err = topics.Load(tid) err = topics.Reload(tid)
if err != nil && err == ErrNoRows { if err != nil && err == ErrNoRows {
LocalError("The destination no longer exists", w, r, user) LocalError("The destination no longer exists", w, r, user)
return return
@ -269,7 +273,7 @@ func routeLikeTopic(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
topic, err := topics.CascadeGet(tid) topic, err := topics.Get(tid)
if err == ErrNoRows { if err == ErrNoRows {
PreError("The requested topic doesn't exist.", w, r) PreError("The requested topic doesn't exist.", w, r)
return return
@ -302,7 +306,7 @@ func routeLikeTopic(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
_, err = users.CascadeGet(topic.CreatedBy) _, err = users.Get(topic.CreatedBy)
if err != nil && err == ErrNoRows { if err != nil && err == ErrNoRows {
LocalError("The target user doesn't exist", w, r, user) LocalError("The target user doesn't exist", w, r, user)
return return
@ -345,7 +349,7 @@ func routeLikeTopic(w http.ResponseWriter, r *http.Request, user User) {
_ = wsHub.pushAlert(topic.CreatedBy, int(lastID), "like", "topic", user.ID, topic.CreatedBy, tid) _ = wsHub.pushAlert(topic.CreatedBy, int(lastID), "like", "topic", user.ID, topic.CreatedBy, tid)
// Reload the topic... // Reload the topic...
err = topics.Load(tid) err = topics.Reload(tid)
if err != nil && err == ErrNoRows { if err != nil && err == ErrNoRows {
LocalError("The liked topic no longer exists", w, r, user) LocalError("The liked topic no longer exists", w, r, user)
return return
@ -413,7 +417,7 @@ func routeReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
_, err = users.CascadeGet(reply.CreatedBy) _, err = users.Get(reply.CreatedBy)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
LocalError("The target user doesn't exist", w, r, user) LocalError("The target user doesn't exist", w, r, user)
return return
@ -459,7 +463,7 @@ func routeReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user User) {
} }
func routeProfileReplyCreate(w http.ResponseWriter, r *http.Request, user User) { func routeProfileReplyCreate(w http.ResponseWriter, r *http.Request, user User) {
if !user.Loggedin || !user.Perms.CreateReply { if !user.Perms.ViewTopic || !user.Perms.CreateReply {
NoPermissions(w, r, user) NoPermissions(w, r, user)
return return
} }
@ -540,7 +544,7 @@ func routeReportSubmit(w http.ResponseWriter, r *http.Request, user User, sitemI
return return
} }
topic, err := topics.CascadeGet(reply.ParentID) topic, err := topics.Get(reply.ParentID)
if err == ErrNoRows { if err == ErrNoRows {
LocalError("We weren't able to find the topic the reported post is supposed to be in", w, r, user) LocalError("We weren't able to find the topic the reported post is supposed to be in", w, r, user)
return return
@ -813,7 +817,7 @@ func routeAccountOwnEditAvatarSubmit(w http.ResponseWriter, r *http.Request, use
return return
} }
user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + "." + ext user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + "." + ext
err = users.Load(user.ID) err = users.Reload(user.ID)
if err != nil { if err != nil {
LocalError("This user no longer exists!", w, r, user) LocalError("This user no longer exists!", w, r, user)
return return
@ -871,7 +875,7 @@ func routeAccountOwnEditUsernameSubmit(w http.ResponseWriter, r *http.Request, u
// TODO: Use the reloaded data instead for the name? // TODO: Use the reloaded data instead for the name?
user.Name = newUsername user.Name = newUsername
err = users.Load(user.ID) err = users.Reload(user.ID)
if err != nil { if err != nil {
LocalError("Your account doesn't exist!", w, r, user) LocalError("Your account doesn't exist!", w, r, user)
return return

View File

@ -15,24 +15,30 @@ func TestUserStore(t *testing.T) {
initPlugins() initPlugins()
} }
users = NewMemoryUserStore(config.UserCacheCapacity)
userStoreTest(t)
users = NewSQLUserStore()
userStoreTest(t)
}
func userStoreTest(t *testing.T) {
var user *User var user *User
var err error var err error
_, err = users.CascadeGet(-1) _, err = users.Get(-1)
if err == nil { if err == nil {
t.Error("UID #-1 shouldn't exist") t.Error("UID #-1 shouldn't exist")
} else if err != ErrNoRows { } else if err != ErrNoRows {
t.Fatal(err) t.Fatal(err)
} }
_, err = users.CascadeGet(0) _, err = users.Get(0)
if err == nil { if err == nil {
t.Error("UID #0 shouldn't exist") t.Error("UID #0 shouldn't exist")
} else if err != ErrNoRows { } else if err != ErrNoRows {
t.Fatal(err) t.Fatal(err)
} }
user, err = users.CascadeGet(1) user, err = users.Get(1)
if err == ErrNoRows { if err == ErrNoRows {
t.Error("Couldn't find UID #1") t.Error("Couldn't find UID #1")
} else if err != nil { } else if err != nil {
@ -45,38 +51,122 @@ func TestUserStore(t *testing.T) {
// TODO: Lock onto the specific error type. Is this even possible without sacrificing the detailed information in the error message? // TODO: Lock onto the specific error type. Is this even possible without sacrificing the detailed information in the error message?
var userList map[int]*User var userList map[int]*User
_, err = users.BulkCascadeGetMap([]int{-1}) userList, _ = users.BulkGetMap([]int{-1})
if err == nil { if len(userList) > 0 {
t.Error("UID #-1 shouldn't exist") t.Error("There shouldn't be any results for UID #-1")
} }
_, err = users.BulkCascadeGetMap([]int{0}) userList, _ = users.BulkGetMap([]int{0})
if err == nil { if len(userList) > 0 {
t.Error("UID #0 shouldn't exist") t.Error("There shouldn't be any results for UID #0")
}
userList, err = users.BulkCascadeGetMap([]int{1})
if err == ErrNoRows {
t.Error("Couldn't find UID #1")
} else if err != nil {
t.Fatal(err)
} }
userList, _ = users.BulkGetMap([]int{1})
if len(userList) == 0 { if len(userList) == 0 {
t.Error("The returned map is empty for UID #0") t.Error("The returned map is empty for UID #1")
} else if len(userList) > 1 { } else if len(userList) > 1 {
t.Error("Too many results were returned for UID #0") t.Error("Too many results were returned for UID #1")
} }
user, ok := userList[1] user, ok := userList[1]
if !ok { if !ok {
t.Error("We couldn't find UID #0 in the returned map") t.Error("We couldn't find UID #1 in the returned map")
t.Error("userList", userList) t.Error("userList", userList)
} }
if user.ID != 1 { if user.ID != 1 {
t.Error("user.ID does not match the requested UID. Got '" + strconv.Itoa(user.ID) + "' instead.") t.Error("user.ID does not match the requested UID. Got '" + strconv.Itoa(user.ID) + "' instead.")
} }
ok = users.Exists(-1)
if ok {
t.Error("UID #-1 shouldn't exist")
}
ok = users.Exists(0)
if ok {
t.Error("UID #0 shouldn't exist")
}
ok = users.Exists(1)
if !ok {
t.Error("UID #1 should exist")
}
count := users.GetGlobalCount()
if count <= 0 {
t.Error("The number of users should be bigger than zero")
t.Error("count", count)
}
}
func TestTopicStore(t *testing.T) {
if !gloinited {
err := gloinit()
if err != nil {
t.Fatal(err)
}
}
if !pluginsInited {
initPlugins()
}
topics = NewMemoryTopicStore(config.TopicCacheCapacity)
topicStoreTest(t)
topics = NewSQLTopicStore()
topicStoreTest(t)
}
func topicStoreTest(t *testing.T) {
var topic *Topic
var err error
_, err = topics.Get(-1)
if err == nil {
t.Error("TID #-1 shouldn't exist")
} else if err != ErrNoRows {
t.Fatal(err)
}
_, err = topics.Get(0)
if err == nil {
t.Error("TID #0 shouldn't exist")
} else if err != ErrNoRows {
t.Fatal(err)
}
topic, err = topics.Get(1)
if err == ErrNoRows {
t.Error("Couldn't find TID #1")
} else if err != nil {
t.Fatal(err)
}
if topic.ID != 1 {
t.Error("topic.ID does not match the requested TID. Got '" + strconv.Itoa(topic.ID) + "' instead.")
}
// TODO: Add BulkGetMap() to the TopicStore
ok := topics.Exists(-1)
if ok {
t.Error("TID #-1 shouldn't exist")
}
ok = topics.Exists(0)
if ok {
t.Error("TID #0 shouldn't exist")
}
ok = topics.Exists(1)
if !ok {
t.Error("TID #1 should exist")
}
count := topics.GetGlobalCount()
if count <= 0 {
t.Error("The number of topics should be bigger than zero")
t.Error("count", count)
}
} }
func TestForumStore(t *testing.T) { func TestForumStore(t *testing.T) {
@ -90,14 +180,14 @@ func TestForumStore(t *testing.T) {
var forum *Forum var forum *Forum
var err error var err error
_, err = fstore.CascadeGet(-1) _, err = fstore.Get(-1)
if err == nil { if err == nil {
t.Error("FID #-1 shouldn't exist") t.Error("FID #-1 shouldn't exist")
} else if err != ErrNoRows { } else if err != ErrNoRows {
t.Fatal(err) t.Fatal(err)
} }
forum, err = fstore.CascadeGet(0) forum, err = fstore.Get(0)
if err == ErrNoRows { if err == ErrNoRows {
t.Error("Couldn't find FID #0") t.Error("Couldn't find FID #0")
} else if err != nil { } else if err != nil {
@ -111,7 +201,7 @@ func TestForumStore(t *testing.T) {
t.Error("FID #0 is named '" + forum.Name + "' and not 'Uncategorised'") t.Error("FID #0 is named '" + forum.Name + "' and not 'Uncategorised'")
} }
forum, err = fstore.CascadeGet(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")
} else if err != nil { } else if err != nil {
@ -119,13 +209,13 @@ func TestForumStore(t *testing.T) {
} }
if forum.ID != 1 { if forum.ID != 1 {
t.Error("forum.ID doesn't not match the requested UID. Got '" + strconv.Itoa(forum.ID) + "' instead.'") t.Error("forum.ID doesn't not match the requested FID. Got '" + strconv.Itoa(forum.ID) + "' instead.'")
} }
if forum.Name != "Reports" { if forum.Name != "Reports" {
t.Error("FID #0 is named '" + forum.Name + "' and not 'Reports'") t.Error("FID #0 is named '" + forum.Name + "' and not 'Reports'")
} }
forum, err = fstore.CascadeGet(2) forum, err = fstore.Get(2)
if err == ErrNoRows { if err == ErrNoRows {
t.Error("Couldn't find FID #2") t.Error("Couldn't find FID #2")
} else if err != nil { } else if err != nil {
@ -133,6 +223,84 @@ func TestForumStore(t *testing.T) {
} }
_ = forum _ = forum
ok := fstore.Exists(-1)
if ok {
t.Error("FID #-1 shouldn't exist")
}
ok = fstore.Exists(0)
if !ok {
t.Error("FID #0 should exist")
}
ok = fstore.Exists(1)
if !ok {
t.Error("FID #1 should exist")
}
}
func TestGroupStore(t *testing.T) {
if !gloinited {
gloinit()
}
if !pluginsInited {
initPlugins()
}
var group *Group
var err error
_, err = gstore.Get(-1)
if err == nil {
t.Error("GID #-1 shouldn't exist")
} else if err != ErrNoRows {
t.Fatal(err)
}
group, err = gstore.Get(0)
if err == ErrNoRows {
t.Error("Couldn't find GID #0")
} else if err != nil {
t.Fatal(err)
}
if group.ID != 0 {
t.Error("group.ID doesn't not match the requested GID. Got '" + strconv.Itoa(group.ID) + "' instead.")
}
if group.Name != "Unknown" {
t.Error("GID #0 is named '" + group.Name + "' and not 'Unknown'")
}
// ? - What if they delete this group? x.x
// ? - Maybe, pick a random group ID? That would take an extra query, and I'm not sure if I want to be rewriting custom test queries. Possibly, a Random() method on the GroupStore? Seems useless for normal use, it might have some merit for the TopicStore though
group, err = gstore.Get(1)
if err == ErrNoRows {
t.Error("Couldn't find GID #1")
} else if err != nil {
t.Fatal(err)
}
if group.ID != 1 {
t.Error("group.ID doesn't not match the requested GID. Got '" + strconv.Itoa(group.ID) + "' instead.'")
}
_ = group
ok := gstore.Exists(-1)
if ok {
t.Error("GID #-1 shouldn't exist")
}
ok = gstore.Exists(0)
if !ok {
t.Error("GID #0 should exist")
}
ok = gstore.Exists(1)
if !ok {
t.Error("GID #1 should exist")
}
} }
func TestSlugs(t *testing.T) { func TestSlugs(t *testing.T) {

View File

@ -28,7 +28,7 @@ func routeEditTopic(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
oldTopic, err := topics.CascadeGet(tid) oldTopic, 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
@ -50,8 +50,9 @@ func routeEditTopic(w http.ResponseWriter, r *http.Request, user User) {
topicName := r.PostFormValue("topic_name") topicName := r.PostFormValue("topic_name")
topicStatus := r.PostFormValue("topic_status") topicStatus := r.PostFormValue("topic_status")
isClosed := (topicStatus == "closed") isClosed := (topicStatus == "closed")
topicContent := html.EscapeString(r.PostFormValue("topic_content")) topicContent := html.EscapeString(r.PostFormValue("topic_content"))
// TODO: Move this bit to the TopicStore
_, err = edit_topic_stmt.Exec(topicName, preparseMessage(topicContent), parseMessage(html.EscapeString(preparseMessage(topicContent))), isClosed, tid) _, err = edit_topic_stmt.Exec(topicName, preparseMessage(topicContent), parseMessage(html.EscapeString(preparseMessage(topicContent))), isClosed, tid)
if err != nil { if err != nil {
InternalErrorJSQ(err, w, r, isJs) InternalErrorJSQ(err, w, r, isJs)
@ -94,7 +95,7 @@ func routeEditTopic(w http.ResponseWriter, r *http.Request, user User) {
} }
} }
err = topics.Load(tid) err = topics.Reload(tid)
if err == ErrNoRows { if err == ErrNoRows {
LocalErrorJSQ("This topic no longer exists!", w, r, user, isJs) LocalErrorJSQ("This topic no longer exists!", w, r, user, isJs)
return return
@ -110,6 +111,7 @@ func routeEditTopic(w http.ResponseWriter, r *http.Request, user User) {
} }
} }
// TODO: Add support for soft-deletion and add a permission just for hard delete
// TODO: Disable stat updates in posts handled by plugin_socialgroups // TODO: Disable stat updates in posts handled by plugin_socialgroups
func routeDeleteTopic(w http.ResponseWriter, r *http.Request, user User) { func routeDeleteTopic(w http.ResponseWriter, r *http.Request, user User) {
tid, err := strconv.Atoi(r.URL.Path[len("/topic/delete/submit/"):]) tid, err := strconv.Atoi(r.URL.Path[len("/topic/delete/submit/"):])
@ -118,7 +120,7 @@ func routeDeleteTopic(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
topic, err := topics.CascadeGet(tid) topic, err := topics.Get(tid)
if err == ErrNoRows { if err == ErrNoRows {
PreError("The topic you tried to delete doesn't exist.", w, r) PreError("The topic you tried to delete doesn't exist.", w, r)
return return
@ -137,7 +139,8 @@ func routeDeleteTopic(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
_, err = delete_topic_stmt.Exec(tid) // We might be able to handle this err better
err = topics.Delete(topic.CreatedBy)
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
return return
@ -154,7 +157,7 @@ func routeDeleteTopic(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
// Might need soft-delete before we can do an action reply for this // ? - We might need to add soft-delete before we can do an action reply for this
/*_, err = create_action_reply_stmt.Exec(tid,"delete",ipaddress,user.ID) /*_, err = create_action_reply_stmt.Exec(tid,"delete",ipaddress,user.ID)
if err != nil { if err != nil {
InternalError(err,w) InternalError(err,w)
@ -163,26 +166,6 @@ func routeDeleteTopic(w http.ResponseWriter, r *http.Request, user User) {
//log.Print("Topic #" + strconv.Itoa(tid) + " was deleted by User #" + strconv.Itoa(user.ID)) //log.Print("Topic #" + strconv.Itoa(tid) + " was deleted by User #" + strconv.Itoa(user.ID))
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
topicCreator, err := users.CascadeGet(topic.CreatedBy)
if err == nil {
wcount := wordCount(topic.Content)
err = topicCreator.decreasePostStats(wcount, true)
if err != nil {
InternalError(err, w)
return
}
} else if err != ErrNoRows {
InternalError(err, w)
return
}
err = fstore.DecrementTopicCount(topic.ParentID)
if err != nil && err != ErrNoRows {
InternalError(err, w)
return
}
topics.Remove(tid)
} }
func routeStickTopic(w http.ResponseWriter, r *http.Request, user User) { func routeStickTopic(w http.ResponseWriter, r *http.Request, user User) {
@ -192,7 +175,7 @@ func routeStickTopic(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
topic, err := topics.CascadeGet(tid) topic, err := topics.Get(tid)
if err == ErrNoRows { if err == ErrNoRows {
PreError("The topic you tried to pin doesn't exist.", w, r) PreError("The topic you tried to pin doesn't exist.", w, r)
return return
@ -233,7 +216,7 @@ func routeStickTopic(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
err = topics.Load(tid) err = topics.Reload(tid)
if err != nil { if err != nil {
LocalError("This topic doesn't exist!", w, r, user) LocalError("This topic doesn't exist!", w, r, user)
return return
@ -248,7 +231,7 @@ func routeUnstickTopic(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
topic, err := topics.CascadeGet(tid) topic, err := topics.Get(tid)
if err == ErrNoRows { if err == ErrNoRows {
PreError("The topic you tried to unpin doesn't exist.", w, r) PreError("The topic you tried to unpin doesn't exist.", w, r)
return return
@ -289,7 +272,7 @@ func routeUnstickTopic(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
err = topics.Load(tid) err = topics.Reload(tid)
if err != nil { if err != nil {
LocalError("This topic doesn't exist!", w, r, user) LocalError("This topic doesn't exist!", w, r, user)
return return
@ -411,7 +394,7 @@ func routeReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user User) {
w.Write(successJSONBytes) w.Write(successJSONBytes)
} }
replyCreator, err := users.CascadeGet(reply.CreatedBy) replyCreator, err := users.Get(reply.CreatedBy)
if err == nil { if err == nil {
wcount := wordCount(reply.Content) wcount := wordCount(reply.Content)
err = replyCreator.decreasePostStats(wcount, false) err = replyCreator.decreasePostStats(wcount, false)
@ -439,7 +422,7 @@ func routeReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
err = topics.Load(reply.ParentID) err = topics.Reload(reply.ParentID)
if err != nil { if err != nil {
LocalError("This topic no longer exists!", w, r, user) LocalError("This topic no longer exists!", w, r, user)
return return
@ -616,7 +599,7 @@ func routeIps(w http.ResponseWriter, r *http.Request, user User) {
} }
// TODO: What if a user is deleted via the Control Panel? // TODO: What if a user is deleted via the Control Panel?
userList, err := users.BulkCascadeGetMap(idSlice) userList, err := users.BulkGetMap(idSlice)
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
return return
@ -693,7 +676,7 @@ func routeBanSubmit(w http.ResponseWriter, r *http.Request, user User) {
return return
}*/ }*/
targetUser, err := users.CascadeGet(uid) targetUser, err := users.Get(uid)
if err == ErrNoRows { if err == ErrNoRows {
LocalError("The user you're trying to ban no longer exists.", w, r, user) LocalError("The user you're trying to ban no longer exists.", w, r, user)
return return
@ -784,7 +767,7 @@ func routeUnban(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
targetUser, err := users.CascadeGet(uid) targetUser, err := users.Get(uid)
if err == ErrNoRows { if err == ErrNoRows {
LocalError("The user you're trying to unban no longer exists.", w, r, user) LocalError("The user you're trying to unban no longer exists.", w, r, user)
return return
@ -874,7 +857,7 @@ func routeActivate(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
err = users.Load(uid) err = users.Reload(uid)
if err != nil { if err != nil {
LocalError("This user no longer exists!", w, r, user) LocalError("This user no longer exists!", w, r, user)
return return

View File

@ -190,7 +190,7 @@ type PanelEditGroupPage struct {
} }
type GroupForumPermPreset struct { type GroupForumPermPreset struct {
Group Group Group *Group
Preset string Preset string
} }
@ -474,7 +474,7 @@ func parseMessage(msg string /*, user User*/) string {
tid, intLen := coerceIntBytes(msgbytes[start:]) tid, intLen := coerceIntBytes(msgbytes[start:])
i += intLen i += intLen
topic, err := topics.CascadeGet(tid) topic, err := topics.Get(tid)
if err != nil || !fstore.Exists(topic.ParentID) { if err != nil || !fstore.Exists(topic.ParentID) {
outbytes = append(outbytes, invalidTopic...) outbytes = append(outbytes, invalidTopic...)
lastItem = i lastItem = i
@ -550,7 +550,7 @@ func parseMessage(msg string /*, user User*/) string {
uid, intLen := coerceIntBytes(msgbytes[start:]) uid, intLen := coerceIntBytes(msgbytes[start:])
i += intLen i += intLen
menUser, err := users.CascadeGet(uid) menUser, err := users.Get(uid)
if err != nil { if err != nil {
outbytes = append(outbytes, invalidProfile...) outbytes = append(outbytes, invalidProfile...)
lastItem = i lastItem = i

View File

@ -244,7 +244,7 @@ func routePanelForumsCreateSubmit(w http.ResponseWriter, r *http.Request, user U
factive := r.PostFormValue("forum-name") factive := r.PostFormValue("forum-name")
active := (factive == "on" || factive == "1") active := (factive == "on" || factive == "1")
_, err = fstore.CreateForum(fname, fdesc, active, fpreset) _, err = fstore.Create(fname, fdesc, active, fpreset)
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
return return
@ -274,7 +274,7 @@ func routePanelForumsDelete(w http.ResponseWriter, r *http.Request, user User, s
return return
} }
forum, err := fstore.CascadeGet(fid) forum, err := fstore.Get(fid)
if err == ErrNoRows { if err == ErrNoRows {
LocalError("The forum you're trying to delete doesn't exist.", w, r, user) LocalError("The forum you're trying to delete doesn't exist.", w, r, user)
return return
@ -318,7 +318,7 @@ func routePanelForumsDeleteSubmit(w http.ResponseWriter, r *http.Request, user U
return return
} }
err = fstore.CascadeDelete(fid) err = fstore.Delete(fid)
if err == ErrNoRows { if err == ErrNoRows {
LocalError("The forum you're trying to delete doesn't exist.", w, r, user) LocalError("The forum you're trying to delete doesn't exist.", w, r, user)
return return
@ -346,7 +346,7 @@ func routePanelForumsEdit(w http.ResponseWriter, r *http.Request, user User, sfi
return return
} }
forum, err := fstore.CascadeGet(fid) forum, err := fstore.Get(fid)
if err == ErrNoRows { if err == ErrNoRows {
LocalError("The forum you're trying to edit doesn't exist.", w, r, user) LocalError("The forum you're trying to edit doesn't exist.", w, r, user)
return return
@ -359,7 +359,12 @@ func routePanelForumsEdit(w http.ResponseWriter, r *http.Request, user User, sfi
forum.Preset = "custom" forum.Preset = "custom"
} }
var glist = groups glist, err := gstore.GetAll()
if err != nil {
InternalError(err, w)
return
}
var gplist []GroupForumPermPreset var gplist []GroupForumPermPreset
for gid, group := range glist { for gid, group := range glist {
if gid == 0 { if gid == 0 {
@ -412,7 +417,7 @@ func routePanelForumsEditSubmit(w http.ResponseWriter, r *http.Request, user Use
forumPreset := stripInvalidPreset(r.PostFormValue("forum_preset")) forumPreset := stripInvalidPreset(r.PostFormValue("forum_preset"))
forumActive := r.PostFormValue("forum_active") forumActive := r.PostFormValue("forum_active")
forum, err := fstore.CascadeGet(fid) forum, err := fstore.Get(fid)
if err == ErrNoRows { if err == ErrNoRows {
LocalErrorJSQ("The forum you're trying to edit doesn't exist.", w, r, user, isJs) LocalErrorJSQ("The forum you're trying to edit doesn't exist.", w, r, user, isJs)
return return
@ -499,7 +504,7 @@ func routePanelForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, use
permPreset := stripInvalidGroupForumPreset(r.PostFormValue("perm_preset")) permPreset := stripInvalidGroupForumPreset(r.PostFormValue("perm_preset"))
fperms, changed := groupForumPresetToForumPerms(permPreset) fperms, changed := groupForumPresetToForumPerms(permPreset)
forum, err := fstore.CascadeGet(fid) forum, err := fstore.Get(fid)
if err == ErrNoRows { if err == ErrNoRows {
LocalErrorJSQ("This forum doesn't exist", w, r, user, isJs) LocalErrorJSQ("This forum doesn't exist", w, r, user, isJs)
return return
@ -512,7 +517,13 @@ func routePanelForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, use
defer forumUpdateMutex.Unlock() defer forumUpdateMutex.Unlock()
if changed { if changed {
permUpdateMutex.Lock() permUpdateMutex.Lock()
groups[gid].Forums[fid] = fperms defer permUpdateMutex.Unlock()
group, err := gstore.Get(gid)
if err != nil {
LocalError("The group whose permissions you're updating doesn't exist.", w, r, user)
return
}
group.Forums[fid] = fperms
perms, err := json.Marshal(fperms) perms, err := json.Marshal(fperms)
if err != nil { if err != nil {
@ -525,7 +536,6 @@ func routePanelForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, use
InternalErrorJSQ(err, w, r, isJs) InternalErrorJSQ(err, w, r, isJs)
return return
} }
permUpdateMutex.Unlock()
_, err = update_forum_stmt.Exec(forum.Name, forum.Desc, forum.Active, "", fid) _, err = update_forum_stmt.Exec(forum.Name, forum.Desc, forum.Active, "", fid)
if err != nil { if err != nil {
@ -1159,8 +1169,8 @@ func routePanelUsers(w http.ResponseWriter, r *http.Request, user User) {
puser.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(puser.ID), 1) puser.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(puser.ID), 1)
} }
if groups[puser.Group].Tag != "" { if gstore.DirtyGet(puser.Group).Tag != "" {
puser.Tag = groups[puser.Group].Tag puser.Tag = gstore.DirtyGet(puser.Group).Tag
} else { } else {
puser.Tag = "" puser.Tag = ""
} }
@ -1202,7 +1212,7 @@ func routePanelUsersEdit(w http.ResponseWriter, r *http.Request, user User, suid
return return
} }
targetUser, err := users.CascadeGet(uid) targetUser, err := users.Get(uid)
if err == ErrNoRows { if err == ErrNoRows {
LocalError("The user you're trying to edit doesn't exist.", w, r, user) LocalError("The user you're trying to edit doesn't exist.", w, r, user)
return return
@ -1216,6 +1226,12 @@ func routePanelUsersEdit(w http.ResponseWriter, r *http.Request, user User, suid
return return
} }
groups, err := gstore.GetRange(1, 0) // ? - 0 = Go to the end
if err != nil {
InternalError(err, w)
return // ? - Should we stop admins from deleting all the groups? Maybe, protect the group they're currently using?
}
var groupList []interface{} var groupList []interface{}
for _, group := range groups[1:] { for _, group := range groups[1:] {
if !user.Perms.EditUserGroupAdmin && group.IsAdmin { if !user.Perms.EditUserGroupAdmin && group.IsAdmin {
@ -1259,7 +1275,7 @@ func routePanelUsersEditSubmit(w http.ResponseWriter, r *http.Request, user User
return return
} }
targetUser, err := users.CascadeGet(uid) targetUser, err := users.Get(uid)
if err == ErrNoRows { if err == ErrNoRows {
LocalError("The user you're trying to edit doesn't exist.", w, r, user) LocalError("The user you're trying to edit doesn't exist.", w, r, user)
return return
@ -1301,16 +1317,20 @@ func routePanelUsersEditSubmit(w http.ResponseWriter, r *http.Request, user User
return return
} }
if (newgroup > groupCapCount) || (newgroup < 0) || groups[newgroup].Name == "" { group, err := gstore.Get(newgroup)
if err == ErrNoRows {
LocalError("The group you're trying to place this user in doesn't exist.", w, r, user) LocalError("The group you're trying to place this user in doesn't exist.", w, r, user)
return return
} else if err != nil {
InternalError(err, w)
return
} }
if !user.Perms.EditUserGroupAdmin && groups[newgroup].IsAdmin { if !user.Perms.EditUserGroupAdmin && group.IsAdmin {
LocalError("You need the EditUserGroupAdmin permission to assign someone to an administrator group.", w, r, user) LocalError("You need the EditUserGroupAdmin permission to assign someone to an administrator group.", w, r, user)
return return
} }
if !user.Perms.EditUserGroupSuperMod && groups[newgroup].IsMod { if !user.Perms.EditUserGroupSuperMod && group.IsMod {
LocalError("You need the EditUserGroupAdmin permission to assign someone to a super mod group.", w, r, user) LocalError("You need the EditUserGroupAdmin permission to assign someone to a super mod group.", w, r, user)
return return
} }
@ -1325,7 +1345,7 @@ func routePanelUsersEditSubmit(w http.ResponseWriter, r *http.Request, user User
SetPassword(targetUser.ID, newpassword) SetPassword(targetUser.ID, newpassword)
} }
err = users.Load(targetUser.ID) err = users.Reload(targetUser.ID)
if err != nil { if err != nil {
LocalError("This user no longer exists!", w, r, user) LocalError("This user no longer exists!", w, r, user)
return return
@ -1349,7 +1369,8 @@ func routePanelGroups(w http.ResponseWriter, r *http.Request, user User) {
var count int var count int
var groupList []GroupAdmin var groupList []GroupAdmin
for _, group := range groups[offset:] { groups, _ := gstore.GetRange(offset, 0)
for _, group := range groups {
if count == perPage { if count == perPage {
break break
} }
@ -1412,13 +1433,16 @@ func routePanelGroupsEdit(w http.ResponseWriter, r *http.Request, user User, sgi
return return
} }
if !groupExists(gid) { group, err := gstore.Get(gid)
if err == ErrNoRows {
//log.Print("aaaaa monsters") //log.Print("aaaaa monsters")
NotFound(w, r) NotFound(w, r)
return return
} else if err != nil {
InternalError(err, w)
return
} }
group := groups[gid]
if group.IsAdmin && !user.Perms.EditGroupAdmin { if group.IsAdmin && !user.Perms.EditGroupAdmin {
LocalError("You need the EditGroupAdmin permission to edit an admin group.", w, r, user) LocalError("You need the EditGroupAdmin permission to edit an admin group.", w, r, user)
return return
@ -1471,13 +1495,16 @@ func routePanelGroupsEditPerms(w http.ResponseWriter, r *http.Request, user User
return return
} }
if !groupExists(gid) { group, err := gstore.Get(gid)
if err == ErrNoRows {
//log.Print("aaaaa monsters") //log.Print("aaaaa monsters")
NotFound(w, r) NotFound(w, r)
return return
} else if err != nil {
InternalError(err, w)
return
} }
group := groups[gid]
if group.IsAdmin && !user.Perms.EditGroupAdmin { if group.IsAdmin && !user.Perms.EditGroupAdmin {
LocalError("You need the EditGroupAdmin permission to edit an admin group.", w, r, user) LocalError("You need the EditGroupAdmin permission to edit an admin group.", w, r, user)
return return
@ -1554,13 +1581,16 @@ func routePanelGroupsEditSubmit(w http.ResponseWriter, r *http.Request, user Use
return return
} }
if !groupExists(gid) { group, err := gstore.Get(gid)
if err == ErrNoRows {
//log.Print("aaaaa monsters") //log.Print("aaaaa monsters")
NotFound(w, r) NotFound(w, r)
return return
} else if err != nil {
InternalError(err, w)
return
} }
group := groups[gid]
if group.IsAdmin && !user.Perms.EditGroupAdmin { if group.IsAdmin && !user.Perms.EditGroupAdmin {
LocalError("You need the EditGroupAdmin permission to edit an admin group.", w, r, user) LocalError("You need the EditGroupAdmin permission to edit an admin group.", w, r, user)
return return
@ -1611,9 +1641,9 @@ func routePanelGroupsEditSubmit(w http.ResponseWriter, r *http.Request, user Use
InternalError(err, w) InternalError(err, w)
return return
} }
groups[gid].IsAdmin = true group.IsAdmin = true
groups[gid].IsMod = true group.IsMod = true
groups[gid].IsBanned = false group.IsBanned = false
case "Mod": case "Mod":
if !user.Perms.EditGroupSuperMod { if !user.Perms.EditGroupSuperMod {
LocalError("You need the EditGroupSuperMod permission to designate this group as a super-mod group.", w, r, user) LocalError("You need the EditGroupSuperMod permission to designate this group as a super-mod group.", w, r, user)
@ -1625,18 +1655,18 @@ func routePanelGroupsEditSubmit(w http.ResponseWriter, r *http.Request, user Use
InternalError(err, w) InternalError(err, w)
return return
} }
groups[gid].IsAdmin = false group.IsAdmin = false
groups[gid].IsMod = true group.IsMod = true
groups[gid].IsBanned = false group.IsBanned = false
case "Banned": case "Banned":
_, err = update_group_rank_stmt.Exec(0, 0, 1, gid) _, err = update_group_rank_stmt.Exec(0, 0, 1, gid)
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
return return
} }
groups[gid].IsAdmin = false group.IsAdmin = false
groups[gid].IsMod = false group.IsMod = false
groups[gid].IsBanned = true group.IsBanned = true
case "Guest": case "Guest":
LocalError("You can't designate a group as a guest group.", w, r, user) LocalError("You can't designate a group as a guest group.", w, r, user)
return return
@ -1646,9 +1676,9 @@ func routePanelGroupsEditSubmit(w http.ResponseWriter, r *http.Request, user Use
InternalError(err, w) InternalError(err, w)
return return
} }
groups[gid].IsAdmin = false group.IsAdmin = false
groups[gid].IsMod = false group.IsMod = false
groups[gid].IsBanned = false group.IsBanned = false
default: default:
LocalError("Invalid group type.", w, r, user) LocalError("Invalid group type.", w, r, user)
return return
@ -1660,8 +1690,8 @@ func routePanelGroupsEditSubmit(w http.ResponseWriter, r *http.Request, user Use
InternalError(err, w) InternalError(err, w)
return return
} }
groups[gid].Name = gname group.Name = gname
groups[gid].Tag = gtag group.Tag = gtag
http.Redirect(w, r, "/panel/groups/edit/"+strconv.Itoa(gid), http.StatusSeeOther) http.Redirect(w, r, "/panel/groups/edit/"+strconv.Itoa(gid), http.StatusSeeOther)
} }
@ -1686,13 +1716,16 @@ func routePanelGroupsEditPermsSubmit(w http.ResponseWriter, r *http.Request, use
return return
} }
if !groupExists(gid) { group, err := gstore.Get(gid)
if err == ErrNoRows {
//log.Print("aaaaa monsters o.o") //log.Print("aaaaa monsters o.o")
NotFound(w, r) NotFound(w, r)
return return
} else if err != nil {
InternalError(err, w)
return
} }
group := groups[gid]
if group.IsAdmin && !user.Perms.EditGroupAdmin { if group.IsAdmin && !user.Perms.EditGroupAdmin {
LocalError("You need the EditGroupAdmin permission to edit an admin group.", w, r, user) LocalError("You need the EditGroupAdmin permission to edit an admin group.", w, r, user)
return return
@ -1784,7 +1817,7 @@ func routePanelGroupsCreateSubmit(w http.ResponseWriter, r *http.Request, user U
} }
} }
gid, err := createGroup(groupName, groupTag, isAdmin, isMod, isBanned) gid, err := gstore.Create(groupName, groupTag, isAdmin, isMod, isBanned)
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
return return
@ -1942,32 +1975,32 @@ func routePanelLogsMod(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
actor, err := users.CascadeGet(actorID) actor, err := users.Get(actorID)
if err != nil { if err != nil {
actor = &User{Name: "Unknown", Link: buildProfileURL("unknown", 0)} actor = &User{Name: "Unknown", Link: buildProfileURL("unknown", 0)}
} }
switch action { switch action {
case "lock": case "lock":
topic, err := topics.CascadeGet(elementID) topic, err := topics.Get(elementID)
if err != nil { if err != nil {
topic = &Topic{Title: "Unknown", Link: buildProfileURL("unknown", 0)} topic = &Topic{Title: "Unknown", Link: buildProfileURL("unknown", 0)}
} }
action = "<a href='" + topic.Link + "'>" + topic.Title + "</a> was locked by <a href='" + actor.Link + "'>" + actor.Name + "</a>" action = "<a href='" + topic.Link + "'>" + topic.Title + "</a> was locked by <a href='" + actor.Link + "'>" + actor.Name + "</a>"
case "unlock": case "unlock":
topic, err := topics.CascadeGet(elementID) topic, err := topics.Get(elementID)
if err != nil { if err != nil {
topic = &Topic{Title: "Unknown", Link: buildProfileURL("unknown", 0)} topic = &Topic{Title: "Unknown", Link: buildProfileURL("unknown", 0)}
} }
action = "<a href='" + topic.Link + "'>" + topic.Title + "</a> was reopened by <a href='" + actor.Link + "'>" + actor.Name + "</a>" action = "<a href='" + topic.Link + "'>" + topic.Title + "</a> was reopened by <a href='" + actor.Link + "'>" + actor.Name + "</a>"
case "stick": case "stick":
topic, err := topics.CascadeGet(elementID) topic, err := topics.Get(elementID)
if err != nil { if err != nil {
topic = &Topic{Title: "Unknown", Link: buildProfileURL("unknown", 0)} topic = &Topic{Title: "Unknown", Link: buildProfileURL("unknown", 0)}
} }
action = "<a href='" + topic.Link + "'>" + topic.Title + "</a> was pinned by <a href='" + actor.Link + "'>" + actor.Name + "</a>" action = "<a href='" + topic.Link + "'>" + topic.Title + "</a> was pinned by <a href='" + actor.Link + "'>" + actor.Name + "</a>"
case "unstick": case "unstick":
topic, err := topics.CascadeGet(elementID) topic, err := topics.Get(elementID)
if err != nil { if err != nil {
topic = &Topic{Title: "Unknown", Link: buildProfileURL("unknown", 0)} topic = &Topic{Title: "Unknown", Link: buildProfileURL("unknown", 0)}
} }
@ -1983,19 +2016,19 @@ func routePanelLogsMod(w http.ResponseWriter, r *http.Request, user User) {
action = "A reply in <a href='" + topic.Link + "'>" + topic.Title + "</a> was deleted by <a href='" + actor.Link + "'>" + actor.Name + "</a>" action = "A reply in <a href='" + topic.Link + "'>" + topic.Title + "</a> was deleted by <a href='" + actor.Link + "'>" + actor.Name + "</a>"
} }
case "ban": case "ban":
targetUser, err := users.CascadeGet(elementID) targetUser, err := users.Get(elementID)
if err != nil { if err != nil {
targetUser = &User{Name: "Unknown", Link: buildProfileURL("unknown", 0)} targetUser = &User{Name: "Unknown", Link: buildProfileURL("unknown", 0)}
} }
action = "<a href='" + targetUser.Link + "'>" + targetUser.Name + "</a> was banned by <a href='" + actor.Link + "'>" + actor.Name + "</a>" action = "<a href='" + targetUser.Link + "'>" + targetUser.Name + "</a> was banned by <a href='" + actor.Link + "'>" + actor.Name + "</a>"
case "unban": case "unban":
targetUser, err := users.CascadeGet(elementID) targetUser, err := users.Get(elementID)
if err != nil { if err != nil {
targetUser = &User{Name: "Unknown", Link: buildProfileURL("unknown", 0)} targetUser = &User{Name: "Unknown", Link: buildProfileURL("unknown", 0)}
} }
action = "<a href='" + targetUser.Link + "'>" + targetUser.Name + "</a> was unbanned by <a href='" + actor.Link + "'>" + actor.Name + "</a>" action = "<a href='" + targetUser.Link + "'>" + targetUser.Name + "</a> was unbanned by <a href='" + actor.Link + "'>" + actor.Name + "</a>"
case "activate": case "activate":
targetUser, err := users.CascadeGet(elementID) targetUser, err := users.Get(elementID)
if err != nil { if err != nil {
targetUser = &User{Name: "Unknown", Link: buildProfileURL("unknown", 0)} targetUser = &User{Name: "Unknown", Link: buildProfileURL("unknown", 0)}
} }

View File

@ -298,6 +298,7 @@ func permmapToQuery(permmap map[string]ForumPerms, fid int) error {
return rebuildForumPermissions(fid) return rebuildForumPermissions(fid)
} }
// TODO: Need a more thread-safe way of doing this. Possibly with sync.Map?
func rebuildForumPermissions(fid int) error { func rebuildForumPermissions(fid int) error {
if dev.DebugMode { if dev.DebugMode {
log.Print("Loading the forum permissions") log.Print("Loading the forum permissions")
@ -336,38 +337,44 @@ func rebuildForumPermissions(fid int) error {
} }
forumPerms[gid][fid] = pperms forumPerms[gid][fid] = pperms
} }
for gid := range groups {
groups, err := gstore.GetAll()
if err != nil {
return err
}
for _, group := range groups {
if dev.DebugMode { if dev.DebugMode {
log.Print("Updating the forum permissions for Group #" + strconv.Itoa(gid)) log.Print("Updating the forum permissions for Group #" + strconv.Itoa(group.ID))
} }
var blankList []ForumPerms var blankList []ForumPerms
var blankIntList []int var blankIntList []int
groups[gid].Forums = blankList group.Forums = blankList
groups[gid].CanSee = blankIntList group.CanSee = blankIntList
for ffid := range forums { for ffid := range forums {
forumPerm, ok := forumPerms[gid][ffid] forumPerm, ok := forumPerms[group.ID][ffid]
if ok { if ok {
//log.Print("Overriding permissions for forum #" + strconv.Itoa(fid)) //log.Print("Overriding permissions for forum #" + strconv.Itoa(fid))
groups[gid].Forums = append(groups[gid].Forums, forumPerm) group.Forums = append(group.Forums, forumPerm)
} else { } else {
//log.Print("Inheriting from default for forum #" + strconv.Itoa(fid)) //log.Print("Inheriting from default for forum #" + strconv.Itoa(fid))
forumPerm = BlankForumPerms forumPerm = BlankForumPerms
groups[gid].Forums = append(groups[gid].Forums, forumPerm) group.Forums = append(group.Forums, forumPerm)
} }
if forumPerm.Overrides { if forumPerm.Overrides {
if forumPerm.ViewTopic { if forumPerm.ViewTopic {
groups[gid].CanSee = append(groups[gid].CanSee, ffid) group.CanSee = append(group.CanSee, ffid)
} }
} else if groups[gid].Perms.ViewTopic { } else if group.Perms.ViewTopic {
groups[gid].CanSee = append(groups[gid].CanSee, ffid) group.CanSee = append(group.CanSee, ffid)
} }
} }
if dev.SuperDebug { if dev.SuperDebug {
log.Printf("groups[gid].CanSee %+v\n", groups[gid].CanSee) log.Printf("group.CanSee %+v\n", group.CanSee)
log.Printf("groups[gid].Forums %+v\n", groups[gid].Forums) log.Printf("group.Forums %+v\n", group.Forums)
log.Print("len(groups[gid].Forums)", len(groups[gid].Forums)) log.Print("len(group.Forums)", len(group.Forums))
} }
} }
return nil return nil
@ -410,30 +417,36 @@ func buildForumPermissions() error {
} }
forumPerms[gid][fid] = pperms forumPerms[gid][fid] = pperms
} }
for gid := range groups {
groups, err := gstore.GetAll()
if err != nil {
return err
}
for _, group := range groups {
if dev.DebugMode { if dev.DebugMode {
log.Print("Adding the forum permissions for Group #" + strconv.Itoa(gid) + " - " + groups[gid].Name) log.Print("Adding the forum permissions for Group #" + strconv.Itoa(group.ID) + " - " + group.Name)
} }
//groups[gid].Forums = append(groups[gid].Forums,BlankForumPerms) // GID 0. No longer needed now that Uncategorised occupies that slot //groups[gid].Forums = append(groups[gid].Forums,BlankForumPerms) // GID 0. No longer needed now that Uncategorised occupies that slot
for fid := range forums { for fid := range forums {
forumPerm, ok := forumPerms[gid][fid] forumPerm, ok := forumPerms[group.ID][fid]
if ok { if ok {
// Override group perms // Override group perms
//log.Print("Overriding permissions for forum #" + strconv.Itoa(fid)) //log.Print("Overriding permissions for forum #" + strconv.Itoa(fid))
groups[gid].Forums = append(groups[gid].Forums, forumPerm) group.Forums = append(group.Forums, forumPerm)
} else { } else {
// Inherit from Group // Inherit from Group
//log.Print("Inheriting from default for forum #" + strconv.Itoa(fid)) //log.Print("Inheriting from default for forum #" + strconv.Itoa(fid))
forumPerm = BlankForumPerms forumPerm = BlankForumPerms
groups[gid].Forums = append(groups[gid].Forums, forumPerm) group.Forums = append(group.Forums, forumPerm)
} }
if forumPerm.Overrides { if forumPerm.Overrides {
if forumPerm.ViewTopic { if forumPerm.ViewTopic {
groups[gid].CanSee = append(groups[gid].CanSee, fid) group.CanSee = append(group.CanSee, fid)
} }
} else if groups[gid].Perms.ViewTopic { } else if group.Perms.ViewTopic {
groups[gid].CanSee = append(groups[gid].CanSee, fid) group.CanSee = append(group.CanSee, fid)
} }
} }
if dev.SuperDebug { if dev.SuperDebug {
@ -547,7 +560,11 @@ func rebuildGroupPermissions(gid int) error {
return err return err
} }
groups[gid].Perms = tmpPerms group, err := gstore.Get(gid)
if err != nil {
return err
}
group.Perms = tmpPerms
return nil return nil
} }

View File

@ -395,7 +395,7 @@ func socialgroupsCreateGroupSubmit(w http.ResponseWriter, r *http.Request, user
} }
// Create the backing forum // Create the backing forum
fid, err := fstore.CreateForum(groupName, "", true, "") fid, err := fstore.Create(groupName, "", true, "")
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
return return

View File

@ -6,7 +6,7 @@
*/ */
package main 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 Reply struct /* Should probably rename this to ReplyUser and rename ReplyShort to Reply */
{ {

View File

@ -157,7 +157,13 @@ func routeTopics(w http.ResponseWriter, r *http.Request, user User) {
var qlist string var qlist string
var fidList []interface{} var fidList []interface{}
group := groups[user.Group] group, err := gstore.Get(user.Group)
if err != nil {
LocalError("Something weird happened", w, r, user)
log.Print("Group #" + strconv.Itoa(user.Group) + " doesn't exist despite being used by User #" + strconv.Itoa(user.ID))
return
}
for _, fid := range group.CanSee { for _, fid := range group.CanSee {
if fstore.DirtyGet(fid).Name != "" { if fstore.DirtyGet(fid).Name != "" {
fidList = append(fidList, strconv.Itoa(fid)) fidList = append(fidList, strconv.Itoa(fid))
@ -232,7 +238,7 @@ func routeTopics(w http.ResponseWriter, r *http.Request, user User) {
} }
// TODO: What if a user is deleted via the Control Panel? // TODO: What if a user is deleted via the Control Panel?
userList, err := users.BulkCascadeGetMap(idSlice) userList, err := users.BulkGetMap(idSlice)
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
return return
@ -279,7 +285,7 @@ func routeForum(w http.ResponseWriter, r *http.Request, user User, sfid string)
} }
// TODO: Fix this double-check // TODO: Fix this double-check
forum, err := fstore.CascadeGet(fid) forum, err := fstore.Get(fid)
if err == ErrNoRows { if err == ErrNoRows {
NotFound(w, r) NotFound(w, r)
return return
@ -347,7 +353,7 @@ func routeForum(w http.ResponseWriter, r *http.Request, user User, sfid string)
} }
// TODO: What if a user is deleted via the Control Panel? // TODO: What if a user is deleted via the Control Panel?
userList, err := users.BulkCascadeGetMap(idSlice) userList, err := users.BulkGetMap(idSlice)
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
return return
@ -387,7 +393,12 @@ func routeForums(w http.ResponseWriter, r *http.Request, user User) {
} }
//log.Print("canSee",canSee) //log.Print("canSee",canSee)
} else { } else {
group := groups[user.Group] group, err := gstore.Get(user.Group)
if err != nil {
LocalError("Something weird happened", w, r, user)
log.Print("Group #" + strconv.Itoa(user.Group) + " doesn't exist despite being used by User #" + strconv.Itoa(user.ID))
return
}
canSee = group.CanSee canSee = group.CanSee
//log.Print("group.CanSee",group.CanSee) //log.Print("group.CanSee",group.CanSee)
} }
@ -471,8 +482,14 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) {
user.Perms.CreateReply = false user.Perms.CreateReply = false
} }
topic.Tag = groups[topic.Group].Tag postGroup, err := gstore.Get(topic.Group)
if groups[topic.Group].IsMod || groups[topic.Group].IsAdmin { if err != nil {
InternalError(err, w)
return
}
topic.Tag = postGroup.Tag
if postGroup.IsMod || postGroup.IsAdmin {
topic.ClassName = config.StaffCss topic.ClassName = config.StaffCss
} }
@ -527,7 +544,13 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) {
replyItem.ContentHtml = parseMessage(replyItem.Content) replyItem.ContentHtml = parseMessage(replyItem.Content)
replyItem.ContentLines = strings.Count(replyItem.Content, "\n") replyItem.ContentLines = strings.Count(replyItem.Content, "\n")
if groups[replyItem.Group].IsMod || groups[replyItem.Group].IsAdmin { postGroup, err = gstore.Get(replyItem.Group)
if err != nil {
InternalError(err, w)
return
}
if postGroup.IsMod || postGroup.IsAdmin {
replyItem.ClassName = config.StaffCss replyItem.ClassName = config.StaffCss
} else { } else {
replyItem.ClassName = "" replyItem.ClassName = ""
@ -541,7 +564,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) {
replyItem.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(replyItem.CreatedBy), 1) replyItem.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(replyItem.CreatedBy), 1)
} }
replyItem.Tag = groups[replyItem.Group].Tag replyItem.Tag = postGroup.Tag
/*if headerVars.Settings["url_tags"] == false { /*if headerVars.Settings["url_tags"] == false {
replyItem.URLName = "" replyItem.URLName = ""
@ -631,7 +654,7 @@ func routeProfile(w http.ResponseWriter, r *http.Request, user User) {
puser = &user puser = &user
} else { } else {
// Fetch the user data // Fetch the user data
puser, err = users.CascadeGet(pid) puser, err = users.Get(pid)
if err == ErrNoRows { if err == ErrNoRows {
NotFound(w, r) NotFound(w, r)
return return
@ -656,8 +679,14 @@ func routeProfile(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
group, err := gstore.Get(replyGroup)
if err != nil {
InternalError(err, w)
return
}
replyLines = strings.Count(replyContent, "\n") replyLines = strings.Count(replyContent, "\n")
if groups[replyGroup].IsMod || groups[replyGroup].IsAdmin { if group.IsMod || group.IsAdmin {
replyClassName = config.StaffCss replyClassName = config.StaffCss
} else { } else {
replyClassName = "" replyClassName = ""
@ -670,8 +699,8 @@ func routeProfile(w http.ResponseWriter, r *http.Request, user User) {
replyAvatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(replyCreatedBy), 1) replyAvatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(replyCreatedBy), 1)
} }
if groups[replyGroup].Tag != "" { if group.Tag != "" {
replyTag = groups[replyGroup].Tag replyTag = group.Tag
} else if puser.ID == replyCreatedBy { } else if puser.ID == replyCreatedBy {
replyTag = "Profile Owner" replyTag = "Profile Owner"
} else { } else {
@ -739,7 +768,7 @@ func routeLoginSubmit(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
userPtr, err := users.CascadeGet(uid) userPtr, err := users.Get(uid)
if err != nil { if err != nil {
LocalError("Bad account", w, r, user) LocalError("Bad account", w, r, user)
return return
@ -848,7 +877,7 @@ func routeRegisterSubmit(w http.ResponseWriter, r *http.Request, user User) {
group = config.ActivationGroup group = config.ActivationGroup
} }
uid, err := users.CreateUser(username, password, email, group, active) uid, err := users.Create(username, password, email, group, active)
if err == errAccountExists { if err == errAccountExists {
LocalError("This username isn't available. Try another.", w, r, user) LocalError("This username isn't available. Try another.", w, r, user)
return return

View File

@ -3,19 +3,23 @@ package main
import ( import (
"html" "html"
"html/template" "html/template"
"log"
"net" "net"
"net/http" "net/http"
"strconv"
"strings" "strings"
) )
// nolint // nolint
var PreRoute func(http.ResponseWriter, *http.Request) (User, bool) = preRoute var PreRoute func(http.ResponseWriter, *http.Request) (User, bool) = preRoute
// TODO: Come up with a better middleware solution
// nolint We need these types so people can tell what they are without scrolling to the bottom of the file // nolint We need these types so people can tell what they are without scrolling to the bottom of the file
var PanelUserCheck func(http.ResponseWriter, *http.Request, *User) (*HeaderVars, PanelStats, bool) = panelUserCheck var PanelUserCheck func(http.ResponseWriter, *http.Request, *User) (*HeaderVars, PanelStats, bool) = panelUserCheck
var SimplePanelUserCheck func(http.ResponseWriter, *http.Request, *User) (*HeaderLite, bool) = simplePanelUserCheck var SimplePanelUserCheck func(http.ResponseWriter, *http.Request, *User) (*HeaderLite, bool) = simplePanelUserCheck
var SimpleForumUserCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *HeaderLite, success bool) = simpleForumUserCheck var SimpleForumUserCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *HeaderLite, success bool) = simpleForumUserCheck
var ForumUserCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars *HeaderVars, success bool) = forumUserCheck var ForumUserCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars *HeaderVars, success bool) = forumUserCheck
var MemberCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, success bool) = memberCheck
var SimpleUserCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, success bool) = simpleUserCheck var SimpleUserCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, success bool) = simpleUserCheck
var UserCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, success bool) = userCheck var UserCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, success bool) = userCheck
@ -58,7 +62,14 @@ func simpleForumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fi
} }
} }
fperms := groups[user.Group].Forums[fid] group, err := gstore.Get(user.Group)
if err != nil {
PreError("Something weird happened", w, r)
log.Print("Group #" + strconv.Itoa(user.Group) + " doesn't exist despite being used by User #" + strconv.Itoa(user.ID))
return
}
fperms := group.Forums[fid]
if fperms.Overrides && !user.IsSuperAdmin { if fperms.Overrides && !user.IsSuperAdmin {
user.Perms.ViewTopic = fperms.ViewTopic user.Perms.ViewTopic = fperms.ViewTopic
user.Perms.LikeItem = fperms.LikeItem user.Perms.LikeItem = fperms.LikeItem
@ -93,7 +104,14 @@ func forumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int)
} }
} }
fperms := groups[user.Group].Forums[fid] group, err := gstore.Get(user.Group)
if err != nil {
PreError("Something weird happened", w, r)
log.Print("Group #" + strconv.Itoa(user.Group) + " doesn't exist despite being used by User #" + strconv.Itoa(user.ID))
return
}
fperms := group.Forums[fid]
//log.Printf("user.Perms: %+v\n", user.Perms) //log.Printf("user.Perms: %+v\n", user.Perms)
//log.Printf("fperms: %+v\n", fperms) //log.Printf("fperms: %+v\n", fperms)
if fperms.Overrides && !user.IsSuperAdmin { if fperms.Overrides && !user.IsSuperAdmin {
@ -201,6 +219,16 @@ func simplePanelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (h
return headerLite, true return headerLite, true
} }
// TODO: Add this to the member routes
func memberCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, success bool) {
headerVars, success = UserCheck(w, r, user)
if !user.Loggedin {
NoPermissions(w, r, *user)
return headerVars, false
}
return headerVars, success
}
// SimpleUserCheck is back from the grave, yay :D // SimpleUserCheck is back from the grave, yay :D
func simpleUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, success bool) { func simpleUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, success bool) {
headerLite = &HeaderLite{ headerLite = &HeaderLite{

View File

@ -35,7 +35,7 @@ func handleExpiredScheduledGroups() error {
if err != nil { if err != nil {
return err return err
} }
_ = users.Load(uid) _ = users.Reload(uid)
} }
return rows.Err() return rows.Err()
} }

View File

@ -453,47 +453,48 @@ var profile_0 = []byte(`
<img src="`) <img src="`)
var profile_1 = []byte(`" class="avatar" /> var profile_1 = []byte(`" class="avatar" />
</div> </div>
<div class="rowitem"> <div class="rowitem">`)
var profile_2 = []byte(`
<span class="profileName">`) <span class="profileName">`)
var profile_2 = []byte(`</span>`) var profile_3 = []byte(`</span>`)
var profile_3 = []byte(`<span class="username" style="float: right;font-weight: normal;">`) var profile_4 = []byte(`<span class="username" style="float: right;font-weight: normal;">`)
var profile_4 = []byte(`</span>`) var profile_5 = []byte(`</span>`)
var profile_5 = []byte(` var profile_6 = []byte(`
</div> </div>
<div class="rowitem passive"> <div class="rowitem passive">
<a class="profile_menu_item">Add Friend</a> <a class="profile_menu_item">Add Friend</a>
</div> </div>
`) `)
var profile_6 = []byte(`<div class="rowitem passive"> var profile_7 = []byte(`<div class="rowitem passive">
`) `)
var profile_7 = []byte(`<a href="/users/unban/`) var profile_8 = []byte(`<a href="/users/unban/`)
var profile_8 = []byte(`?session=`) var profile_9 = []byte(`?session=`)
var profile_9 = []byte(`" class="profile_menu_item">Unban</a> var profile_10 = []byte(`" class="profile_menu_item">Unban</a>
`) `)
var profile_10 = []byte(`<a href="#ban_user" class="profile_menu_item">Ban</a>`) var profile_11 = []byte(`<a href="#ban_user" class="profile_menu_item">Ban</a>`)
var profile_11 = []byte(`
</div>`)
var profile_12 = []byte(` var profile_12 = []byte(`
</div>`)
var profile_13 = []byte(`
<div class="rowitem passive"> <div class="rowitem passive">
<a href="/report/submit/`) <a href="/report/submit/`)
var profile_13 = []byte(`?session=`) var profile_14 = []byte(`?session=`)
var profile_14 = []byte(`&type=user" class="profile_menu_item report_item">Report</a> var profile_15 = []byte(`&type=user" class="profile_menu_item report_item">Report</a>
</div> </div>
</div> </div>
</div> </div>
<div id="profile_right_lane" class="colstack_right"> <div id="profile_right_lane" class="colstack_right">
`) `)
var profile_15 = []byte(` var profile_16 = []byte(`
<!-- TODO: Inline the display: none; CSS --> <!-- TODO: Inline the display: none; CSS -->
<div id="ban_user_head" class="colstack_item colstack_head hash_hide ban_user_hash" style="display: none;"> <div id="ban_user_head" class="colstack_item colstack_head hash_hide ban_user_hash" style="display: none;">
<div class="rowitem"><h1>Ban User</h1></div> <div class="rowitem"><h1>Ban User</h1></div>
</div> </div>
<form id="ban_user_form" class="hash_hide ban_user_hash" action="/users/ban/submit/`) <form id="ban_user_form" class="hash_hide ban_user_hash" action="/users/ban/submit/`)
var profile_16 = []byte(`?session=`) var profile_17 = []byte(`?session=`)
var profile_17 = []byte(`" method="post" style="display: none;"> var profile_18 = []byte(`" method="post" style="display: none;">
`) `)
var profile_18 = []byte(` var profile_19 = []byte(`
<div class="colline">If all the fields are left blank, the ban will be permanent.</div> <div class="colline">If all the fields are left blank, the ban will be permanent.</div>
<div class="colstack_item"> <div class="colstack_item">
<div class="formrow real_first_child"> <div class="formrow real_first_child">
@ -524,54 +525,53 @@ var profile_18 = []byte(`
</div> </div>
</form> </form>
`) `)
var profile_19 = []byte(` var profile_20 = []byte(`
<div id="profile_comments_head" class="colstack_item colstack_head hash_hide"> <div id="profile_comments_head" class="colstack_item colstack_head hash_hide">
<div class="rowitem"><h1>Comments</h1></div> <div class="rowitem"><h1>Comments</h1></div>
</div> </div>
<div id="profile_comments" class="colstack_item hash_hide" style="overflow: hidden;border-top: none;">`) <div id="profile_comments" class="colstack_item hash_hide">`)
var profile_20 = []byte(` var profile_21 = []byte(`
<div class="rowitem passive deletable_block editable_parent simple `) <div class="rowitem passive deletable_block editable_parent simple `)
var profile_21 = []byte(`" style="`) var profile_22 = []byte(`" style="`)
var profile_22 = []byte(`background-image: url(`) var profile_23 = []byte(`background-image: url(`)
var profile_23 = []byte(`), url(/static/post-avatar-bg.jpg);background-position: 0px `) var profile_24 = []byte(`), url(/static/post-avatar-bg.jpg);background-position: 0px `)
var profile_24 = []byte(`-1`) var profile_25 = []byte(`-1`)
var profile_25 = []byte(`0px;`) var profile_26 = []byte(`0px;`)
var profile_26 = []byte(`"> var profile_27 = []byte(`">
<span class="editable_block user_content simple">`) <span class="editable_block user_content simple">`)
var profile_27 = []byte(`</span> var profile_28 = []byte(`</span>
<span class="controls"> <span class="controls">
<a href="`) <a href="`)
var profile_28 = []byte(`" class="real_username username">`) var profile_29 = []byte(`" class="real_username username">`)
var profile_29 = []byte(`</a>&nbsp;&nbsp; var profile_30 = []byte(`</a>&nbsp;&nbsp;
`) `)
var profile_30 = []byte(`<a href="/profile/reply/edit/submit/`) var profile_31 = []byte(`<a href="/profile/reply/edit/submit/`)
var profile_31 = []byte(`" class="mod_button" title="Edit Item"><button class="username edit_item edit_label"></button></a> var profile_32 = []byte(`" class="mod_button" title="Edit Item"><button class="username edit_item edit_label"></button></a>
<a href="/profile/reply/delete/submit/`) <a href="/profile/reply/delete/submit/`)
var profile_32 = []byte(`" class="mod_button" title="Delete Item"><button class="username delete_item trash_label"></button></a>`) var profile_33 = []byte(`" class="mod_button" title="Delete Item"><button class="username delete_item trash_label"></button></a>`)
var profile_33 = []byte(` var profile_34 = []byte(`
<a class="mod_button" href="/report/submit/`) <a class="mod_button" href="/report/submit/`)
var profile_34 = []byte(`?session=`) var profile_35 = []byte(`?session=`)
var profile_35 = []byte(`&type=user-reply"><button class="username report_item flag_label"></button></a> var profile_36 = []byte(`&type=user-reply"><button class="username report_item flag_label"></button></a>
`) `)
var profile_36 = []byte(`<a class="username hide_on_mobile user_tag" style="float: right;">`) var profile_37 = []byte(`<a class="username hide_on_mobile user_tag" style="float: right;">`)
var profile_37 = []byte(`</a>`) var profile_38 = []byte(`</a>`)
var profile_38 = []byte(` var profile_39 = []byte(`
</span> </span>
</div> </div>
`) `)
var profile_39 = []byte(`</div> var profile_40 = []byte(`</div>
`) `)
var profile_40 = []byte(` var profile_41 = []byte(`
<form id="profile_comments_form" class="hash_hide" action="/profile/reply/create/" method="post"> <form id="profile_comments_form" class="hash_hide" action="/profile/reply/create/" method="post">
<input name="uid" value='`) <input name="uid" value='`)
var profile_41 = []byte(`' type="hidden" /> var profile_42 = []byte(`' type="hidden" />
<div class="colstack_item topic_reply_form" style="border-top: none;"> <div class="colstack_item topic_reply_form" style="border-top: none;">
<div class="formrow"> <div class="formrow">
<div class="formitem"><textarea name="reply-content" placeholder="Insert reply here"></textarea></div> <div class="formitem"><textarea name="reply-content" placeholder="Insert reply here"></textarea></div>
@ -582,11 +582,11 @@ var profile_41 = []byte(`' type="hidden" />
</div> </div>
</form> </form>
`) `)
var profile_42 = []byte(` var profile_43 = []byte(`
</div> </div>
`) `)
var profile_43 = []byte(` var profile_44 = []byte(`
<script type="text/javascript"> <script type="text/javascript">
function handle_profile_hashbit() { function handle_profile_hashbit() {
var hash_class = "" var hash_class = ""

View File

@ -73,90 +73,91 @@ w.Write(header_16)
w.Write(profile_0) w.Write(profile_0)
w.Write([]byte(tmpl_profile_vars.ProfileOwner.Avatar)) w.Write([]byte(tmpl_profile_vars.ProfileOwner.Avatar))
w.Write(profile_1) w.Write(profile_1)
w.Write([]byte(tmpl_profile_vars.ProfileOwner.Name))
w.Write(profile_2) w.Write(profile_2)
if tmpl_profile_vars.ProfileOwner.Tag != "" { w.Write([]byte(tmpl_profile_vars.ProfileOwner.Name))
w.Write(profile_3) w.Write(profile_3)
w.Write([]byte(tmpl_profile_vars.ProfileOwner.Tag)) if tmpl_profile_vars.ProfileOwner.Tag != "" {
w.Write(profile_4) w.Write(profile_4)
} w.Write([]byte(tmpl_profile_vars.ProfileOwner.Tag))
w.Write(profile_5) w.Write(profile_5)
if tmpl_profile_vars.CurrentUser.IsSuperMod && !tmpl_profile_vars.ProfileOwner.IsSuperMod {
w.Write(profile_6)
if tmpl_profile_vars.ProfileOwner.IsBanned {
w.Write(profile_7)
w.Write([]byte(strconv.Itoa(tmpl_profile_vars.ProfileOwner.ID)))
w.Write(profile_8)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write(profile_9)
} else {
w.Write(profile_10)
} }
w.Write(profile_6)
if tmpl_profile_vars.CurrentUser.IsSuperMod && !tmpl_profile_vars.ProfileOwner.IsSuperMod {
w.Write(profile_7)
if tmpl_profile_vars.ProfileOwner.IsBanned {
w.Write(profile_8)
w.Write([]byte(strconv.Itoa(tmpl_profile_vars.ProfileOwner.ID)))
w.Write(profile_9)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write(profile_10)
} else {
w.Write(profile_11) w.Write(profile_11)
} }
w.Write(profile_12) w.Write(profile_12)
w.Write([]byte(strconv.Itoa(tmpl_profile_vars.ProfileOwner.ID)))
w.Write(profile_13)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write(profile_14)
if tmpl_profile_vars.CurrentUser.Perms.BanUsers {
w.Write(profile_15)
w.Write([]byte(strconv.Itoa(tmpl_profile_vars.ProfileOwner.ID)))
w.Write(profile_16)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write(profile_17)
w.Write(profile_18)
} }
w.Write(profile_13)
w.Write([]byte(strconv.Itoa(tmpl_profile_vars.ProfileOwner.ID)))
w.Write(profile_14)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write(profile_15)
if tmpl_profile_vars.CurrentUser.Perms.BanUsers {
w.Write(profile_16)
w.Write([]byte(strconv.Itoa(tmpl_profile_vars.ProfileOwner.ID)))
w.Write(profile_17)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write(profile_18)
w.Write(profile_19) w.Write(profile_19)
}
w.Write(profile_20)
if len(tmpl_profile_vars.ItemList) != 0 { if len(tmpl_profile_vars.ItemList) != 0 {
for _, item := range tmpl_profile_vars.ItemList { for _, item := range tmpl_profile_vars.ItemList {
w.Write(profile_20)
w.Write([]byte(item.ClassName))
w.Write(profile_21) w.Write(profile_21)
if item.Avatar != "" { w.Write([]byte(item.ClassName))
w.Write(profile_22) w.Write(profile_22)
w.Write([]byte(item.Avatar)) if item.Avatar != "" {
w.Write(profile_23) w.Write(profile_23)
if item.ContentLines <= 5 { w.Write([]byte(item.Avatar))
w.Write(profile_24) w.Write(profile_24)
} if item.ContentLines <= 5 {
w.Write(profile_25) w.Write(profile_25)
} }
w.Write(profile_26) w.Write(profile_26)
w.Write([]byte(item.ContentHtml)) }
w.Write(profile_27) w.Write(profile_27)
w.Write([]byte(item.UserLink)) w.Write([]byte(item.ContentHtml))
w.Write(profile_28) w.Write(profile_28)
w.Write([]byte(item.CreatedByName)) w.Write([]byte(item.UserLink))
w.Write(profile_29) w.Write(profile_29)
if tmpl_profile_vars.CurrentUser.IsMod { w.Write([]byte(item.CreatedByName))
w.Write(profile_30) w.Write(profile_30)
w.Write([]byte(strconv.Itoa(item.ID))) if tmpl_profile_vars.CurrentUser.IsMod {
w.Write(profile_31) w.Write(profile_31)
w.Write([]byte(strconv.Itoa(item.ID))) w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(profile_32) w.Write(profile_32)
}
w.Write(profile_33)
w.Write([]byte(strconv.Itoa(item.ID))) w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(profile_34) w.Write(profile_33)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write(profile_35)
if item.Tag != "" {
w.Write(profile_36)
w.Write([]byte(item.Tag))
w.Write(profile_37)
} }
w.Write(profile_34)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(profile_35)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write(profile_36)
if item.Tag != "" {
w.Write(profile_37)
w.Write([]byte(item.Tag))
w.Write(profile_38) w.Write(profile_38)
} }
}
w.Write(profile_39) w.Write(profile_39)
if !tmpl_profile_vars.CurrentUser.IsBanned {
w.Write(profile_40)
w.Write([]byte(strconv.Itoa(tmpl_profile_vars.ProfileOwner.ID)))
w.Write(profile_41)
} }
}
w.Write(profile_40)
if !tmpl_profile_vars.CurrentUser.IsBanned {
w.Write(profile_41)
w.Write([]byte(strconv.Itoa(tmpl_profile_vars.ProfileOwner.ID)))
w.Write(profile_42) w.Write(profile_42)
}
w.Write(profile_43) w.Write(profile_43)
w.Write(profile_44)
w.Write(footer_0) w.Write(footer_0)
if len(tmpl_profile_vars.Header.Themes) != 0 { if len(tmpl_profile_vars.Header.Themes) != 0 {
for _, item := range tmpl_profile_vars.Header.Themes { for _, item := range tmpl_profile_vars.Header.Themes {

View File

@ -8,7 +8,7 @@
<div class="rowitem avatarRow" style="padding: 0;"> <div class="rowitem avatarRow" style="padding: 0;">
<img src="{{.ProfileOwner.Avatar}}" class="avatar" /> <img src="{{.ProfileOwner.Avatar}}" class="avatar" />
</div> </div>
<div class="rowitem"> <div class="rowitem">{{/** TODO: Stop inlining this CSS **/}}
<span class="profileName">{{.ProfileOwner.Name}}</span>{{if .ProfileOwner.Tag}}<span class="username" style="float: right;font-weight: normal;">{{.ProfileOwner.Tag}}</span>{{end}} <span class="profileName">{{.ProfileOwner.Name}}</span>{{if .ProfileOwner.Tag}}<span class="username" style="float: right;font-weight: normal;">{{.ProfileOwner.Tag}}</span>{{end}}
</div> </div>
<div class="rowitem passive"> <div class="rowitem passive">
@ -30,7 +30,7 @@
<div id="ban_user_head" class="colstack_item colstack_head hash_hide ban_user_hash" style="display: none;"> <div id="ban_user_head" class="colstack_item colstack_head hash_hide ban_user_hash" style="display: none;">
<div class="rowitem"><h1>Ban User</h1></div> <div class="rowitem"><h1>Ban User</h1></div>
</div> </div>
<form id="ban_user_form" class="hash_hide ban_user_hash" action="/users/ban/submit/{{.ProfileOwner.ID}}?session={{.CurrentUser.Session}}" method="post" style="display: none;"> <form id="ban_user_form" class="hash_hide ban_user_hash" action="/users/ban/submit/{{.ProfileOwner.ID}}?session={{.CurrentUser.Session}}" method="post" style="display: none;">
{{/** TODO: Put a JS duration calculator here instead of this text? **/}} {{/** TODO: Put a JS duration calculator here instead of this text? **/}}
<div class="colline">If all the fields are left blank, the ban will be permanent.</div> <div class="colline">If all the fields are left blank, the ban will be permanent.</div>
<div class="colstack_item"> <div class="colstack_item">
@ -66,10 +66,9 @@
<div id="profile_comments_head" class="colstack_item colstack_head hash_hide"> <div id="profile_comments_head" class="colstack_item colstack_head hash_hide">
<div class="rowitem"><h1>Comments</h1></div> <div class="rowitem"><h1>Comments</h1></div>
</div> </div>
<div id="profile_comments" class="colstack_item hash_hide" style="overflow: hidden;border-top: none;">{{range .ItemList}} <div id="profile_comments" class="colstack_item hash_hide">{{range .ItemList}}
<div class="rowitem passive deletable_block editable_parent simple {{.ClassName}}" style="{{if .Avatar}}background-image: url({{.Avatar}}), url(/static/post-avatar-bg.jpg);background-position: 0px {{if le .ContentLines 5}}-1{{end}}0px;{{end}}"> <div class="rowitem passive deletable_block editable_parent simple {{.ClassName}}" style="{{if .Avatar}}background-image: url({{.Avatar}}), url(/static/post-avatar-bg.jpg);background-position: 0px {{if le .ContentLines 5}}-1{{end}}0px;{{end}}">
<span class="editable_block user_content simple">{{.ContentHtml}}</span> <span class="editable_block user_content simple">{{.ContentHtml}}</span>
<span class="controls"> <span class="controls">
<a href="{{.UserLink}}" class="real_username username">{{.CreatedByName}}</a>&nbsp;&nbsp; <a href="{{.UserLink}}" class="real_username username">{{.CreatedByName}}</a>&nbsp;&nbsp;

View File

@ -190,9 +190,6 @@ li a {
} }
} }
.rowlist {
font-size: 14px;
}
.rowlist .rowitem { .rowlist .rowitem {
padding-top: 10px; padding-top: 10px;
padding-bottom: 10px; padding-bottom: 10px;
@ -206,12 +203,12 @@ li a {
.colstack_left { .colstack_left {
float: left; float: left;
width: 30%; width: 30%;
margin-right: 8px; margin-right: 10px;
} }
.colstack_right { .colstack_right {
float: left; float: left;
width: 65%; width: 65%;
width: calc(70% - 15px); width: calc(70% - 13px);
} }
.colstack_item { .colstack_item {
border: 1px solid hsl(0,0%,80%); border: 1px solid hsl(0,0%,80%);
@ -458,11 +455,7 @@ button.username {
} }
/* We'll be rewriting the profiles soon too! */ /* We'll be rewriting the profiles soon too! */
/*.username.real_username { /*.username.real_username { color: #404040; font-size: 16px; padding-right: 4px; }
color: #404040;
font-size: 16px;
padding-right: 4px;
}
.username.real_username:hover { color: black; }*/ .username.real_username:hover { color: black; }*/
.postQuote { .postQuote {
@ -564,11 +557,41 @@ button.username {
background: none; background: none;
} }
#profile_left_lane {
border: 1px solid hsl(0,0%,80%);
width: 220px;
margin-bottom: 10px;
border-bottom: 1.5px inset hsl(0,0%,80%);
}
#profile_left_lane .avatarRow {
overflow: hidden;
max-height: 220px;
}
#profile_left_lane .avatar {
width: 100%;
margin: 0;
display: block;
}
#profile_right_lane {
width: calc(100% - 230px);
}
#profile_comments {
overflow: hidden;
margin-bottom: 0;
}
#profile_comments .rowitem { #profile_comments .rowitem {
background-repeat: no-repeat, repeat-y; background-repeat: no-repeat, repeat-y;
background-size: 128px; background-size: 128px;
padding-left: 136px; padding-left: 136px;
} }
#profile_comments .controls {
width: 100%;
display: inline-block;
margin-top: 20px;
}
#profile_right_lane .topic_reply_form {
border-bottom: 1.5px inset hsl(0,0%,80%);
}
.simple { .simple {
background-color: white; background-color: white;
@ -607,7 +630,7 @@ button.username {
position: sticky; position: sticky;
top: 4px; top: 4px;
/*box-shadow: 0 1px 2px rgba(0,0,0,.1);*/ /*box-shadow: 0 1px 2px rgba(0,0,0,.1);*/
border-bottom: 1px inset hsl(0,0%,80%); border-bottom: 1.5px inset hsl(0,0%,80%);
} }
.userinfo .avatar_item { .userinfo .avatar_item {
background-repeat: no-repeat, repeat-y; background-repeat: no-repeat, repeat-y;
@ -714,13 +737,13 @@ button.username {
/* Firefox specific CSS */ /* Firefox specific CSS */
@supports (-moz-appearance: none) { @supports (-moz-appearance: none) {
.footer { .footer, .rowmenu, #profile_right_lane .topic_reply_form {
border-bottom: 2px inset hsl(0,0%,40%); border-bottom: 2px inset hsl(0,0%,40%);
} }
} }
/* Edge... We can't get the exact shade here, because of how they implemented it x.x */ /* Edge... We can't get the exact shade here, because of how they implemented it x.x */
@supports (-ms-ime-align:auto) { @supports (-ms-ime-align:auto) {
.footer { .footer, .rowmenu, #profile_right_lane .topic_reply_form {
border-bottom: 1.5px inset hsl(0,0%,100%); border-bottom: 1.5px inset hsl(0,0%,100%);
} }
} }

View File

@ -19,7 +19,7 @@
padding-bottom: 2px; padding-bottom: 2px;
color: black; color: black;
font-size: 12px; font-size: 13px;
} }
.panel_floater { .panel_floater {

View File

@ -625,7 +625,9 @@ button.username {
/* Profiles */ /* Profiles */
#profile_left_lane { #profile_left_lane {
width: 220px; width: 220px;
margin-top: 5px; }
#profile_left_pane {
margin-bottom: 12px;
} }
#profile_left_lane .avatarRow { #profile_left_lane .avatarRow {
overflow: hidden; overflow: hidden;
@ -647,6 +649,10 @@ button.username {
#profile_right_lane { #profile_right_lane {
width: calc(100% - 245px); width: calc(100% - 245px);
} }
#profile_comments {
overflow: hidden;
border-top: none;
}
.simple .user_tag { .simple .user_tag {
font-size: 14px; font-size: 14px;
} }

View File

@ -90,23 +90,26 @@ type TopicsRow struct {
ForumLink string ForumLink string
} }
// 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) {
if config.CacheTopicUser != CACHE_SQL { tcache, tok := topics.(TopicCache)
topic, err := topics.Get(tid) ucache, uok := users.(UserCache)
if tok && uok {
topic, err := tcache.CacheGet(tid)
if err == nil { if err == nil {
user, err := users.CascadeGet(topic.CreatedBy) user, err := users.Get(topic.CreatedBy)
if err != nil { if err != nil {
return TopicUser{ID: tid}, err return TopicUser{ID: tid}, err
} }
// 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 users.GetLength() < users.GetCapacity() { } else if ucache.GetLength() < ucache.GetCapacity() {
topic, err = topics.CascadeGet(tid) topic, err = topics.Get(tid)
if err != nil { if err != nil {
return TopicUser{ID: tid}, err return TopicUser{ID: tid}, err
} }
user, err := users.CascadeGet(topic.CreatedBy) user, err := users.Get(topic.CreatedBy)
if err != nil { if err != nil {
return TopicUser{ID: tid}, err return TopicUser{ID: tid}, err
} }
@ -118,11 +121,13 @@ func getTopicuser(tid int) (TopicUser, error) {
err := get_topic_user_stmt.QueryRow(tid).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IPAddress, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level) err := get_topic_user_stmt.QueryRow(tid).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IPAddress, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
tu.Link = buildTopicURL(nameToSlug(tu.Title), tu.ID) tu.Link = buildTopicURL(nameToSlug(tu.Title), tu.ID)
tu.UserLink = buildProfileURL(nameToSlug(tu.CreatedByName), tu.CreatedBy) tu.UserLink = buildProfileURL(nameToSlug(tu.CreatedByName), tu.CreatedBy)
tu.Tag = gstore.DirtyGet(tu.Group).Tag
theTopic := Topic{ID: tu.ID, Link: tu.Link, Title: tu.Title, Content: tu.Content, CreatedBy: tu.CreatedBy, IsClosed: tu.IsClosed, Sticky: tu.Sticky, CreatedAt: tu.CreatedAt, LastReplyAt: tu.LastReplyAt, ParentID: tu.ParentID, IPAddress: tu.IPAddress, PostCount: tu.PostCount, LikeCount: tu.LikeCount} if tok {
//log.Printf("the_topic: %+v\n", the_topic) theTopic := Topic{ID: tu.ID, Link: tu.Link, Title: tu.Title, Content: tu.Content, CreatedBy: tu.CreatedBy, IsClosed: tu.IsClosed, Sticky: tu.Sticky, CreatedAt: tu.CreatedAt, LastReplyAt: tu.LastReplyAt, ParentID: tu.ParentID, IPAddress: tu.IPAddress, PostCount: tu.PostCount, LikeCount: tu.LikeCount}
tu.Tag = groups[tu.Group].Tag //log.Printf("the_topic: %+v\n", theTopic)
_ = topics.Add(&theTopic) _ = tcache.CacheAdd(&theTopic)
}
return tu, err return tu, err
} }

View File

@ -6,52 +6,76 @@
*/ */
package main package main
import "log" import (
import "sync" "database/sql"
import "database/sql" "log"
import "./query_gen/lib" "sync"
"./query_gen/lib"
)
// TODO: Add the watchdog goroutine // TODO: Add the watchdog goroutine
// TODO: Add BulkGetMap
// TODO: Add some sort of update method
var topics TopicStore var topics TopicStore
type TopicStore interface { type TopicStore interface {
Load(id int) error Reload(id int) error // ? - Should we move this to TopicCache? Might require us to do a lot more casting in Gosora though...
Get(id int) (*Topic, error) Get(id int) (*Topic, error)
GetUnsafe(id int) (*Topic, error)
CascadeGet(id int) (*Topic, error)
BypassGet(id int) (*Topic, error) BypassGet(id int) (*Topic, error)
Set(item *Topic) error Delete(id int) error
Add(item *Topic) error Exists(id int) bool
AddUnsafe(item *Topic) error
Remove(id int) error
RemoveUnsafe(id int) error
AddLastTopic(item *Topic, fid int) error AddLastTopic(item *Topic, fid int) error
GetGlobalCount() int
}
type TopicCache interface {
CacheGet(id int) (*Topic, error)
GetUnsafe(id int) (*Topic, error)
CacheSet(item *Topic) error
CacheAdd(item *Topic) error
CacheAddUnsafe(item *Topic) error
CacheRemove(id int) error
CacheRemoveUnsafe(id int) error
GetLength() int GetLength() int
SetCapacity(capacity int)
GetCapacity() int GetCapacity() int
} }
type MemoryTopicStore struct { type MemoryTopicStore struct {
items map[int]*Topic items map[int]*Topic
length int length int
capacity int capacity int
get *sql.Stmt get *sql.Stmt
exists *sql.Stmt
topicCount *sql.Stmt
sync.RWMutex sync.RWMutex
} }
// 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 {
stmt, 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, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data", "tid = ?", "", "")
if err != nil {
log.Fatal(err)
}
existsStmt, err := qgen.Builder.SimpleSelect("topics", "tid", "tid = ?", "", "")
if err != nil {
log.Fatal(err)
}
topicCountStmt, err := qgen.Builder.SimpleCount("topics", "", "")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
return &MemoryTopicStore{ return &MemoryTopicStore{
items: make(map[int]*Topic), items: make(map[int]*Topic),
capacity: capacity, capacity: capacity,
get: stmt, get: getStmt,
exists: existsStmt,
topicCount: topicCountStmt,
} }
} }
func (sts *MemoryTopicStore) Get(id int) (*Topic, error) { func (sts *MemoryTopicStore) CacheGet(id int) (*Topic, error) {
sts.RLock() sts.RLock()
item, ok := sts.items[id] item, ok := sts.items[id]
sts.RUnlock() sts.RUnlock()
@ -61,7 +85,7 @@ func (sts *MemoryTopicStore) Get(id int) (*Topic, error) {
return item, ErrNoRows return item, ErrNoRows
} }
func (sts *MemoryTopicStore) GetUnsafe(id int) (*Topic, error) { func (sts *MemoryTopicStore) CacheGetUnsafe(id int) (*Topic, error) {
item, ok := sts.items[id] item, ok := sts.items[id]
if ok { if ok {
return item, nil return item, nil
@ -69,7 +93,7 @@ func (sts *MemoryTopicStore) GetUnsafe(id int) (*Topic, error) {
return item, ErrNoRows return item, ErrNoRows
} }
func (sts *MemoryTopicStore) CascadeGet(id int) (*Topic, error) { func (sts *MemoryTopicStore) Get(id int) (*Topic, error) {
sts.RLock() sts.RLock()
topic, ok := sts.items[id] topic, ok := sts.items[id]
sts.RUnlock() sts.RUnlock()
@ -81,7 +105,7 @@ func (sts *MemoryTopicStore) CascadeGet(id int) (*Topic, error) {
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
if err == nil { if err == nil {
topic.Link = buildTopicURL(nameToSlug(topic.Title), id) topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
_ = sts.Add(topic) _ = sts.CacheAdd(topic)
} }
return topic, err return topic, err
} }
@ -93,19 +117,58 @@ func (sts *MemoryTopicStore) BypassGet(id int) (*Topic, error) {
return topic, err return topic, err
} }
func (sts *MemoryTopicStore) Load(id int) error { func (sts *MemoryTopicStore) Reload(id int) error {
topic := &Topic{ID: id} topic := &Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
if err == nil { if err == nil {
topic.Link = buildTopicURL(nameToSlug(topic.Title), id) topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
_ = sts.Set(topic) _ = sts.CacheSet(topic)
} else { } else {
_ = sts.Remove(id) _ = sts.CacheRemove(id)
} }
return err return err
} }
func (sts *MemoryTopicStore) Set(item *Topic) error { // TODO: Use a transaction here
func (sts *MemoryTopicStore) Delete(id int) error {
topic, err := sts.Get(id)
if err != nil {
return nil // Already gone, maybe we should check for other errors here
}
topicCreator, err := users.Get(topic.CreatedBy)
if err == nil {
wcount := wordCount(topic.Content)
err = topicCreator.decreasePostStats(wcount, true)
if err != nil {
return err
}
} else if err != ErrNoRows {
return err
}
err = fstore.DecrementTopicCount(topic.ParentID)
if err != nil && err != ErrNoRows {
return err
}
sts.Lock()
sts.CacheRemoveUnsafe(id)
_, err = delete_topic_stmt.Exec(id)
if err != nil {
sts.Unlock()
return err
}
sts.Unlock()
return nil
}
func (sts *MemoryTopicStore) Exists(id int) bool {
return sts.exists.QueryRow(id).Scan(&id) == nil
}
func (sts *MemoryTopicStore) CacheSet(item *Topic) error {
sts.Lock() sts.Lock()
_, ok := sts.items[item.ID] _, ok := sts.items[item.ID]
if ok { if ok {
@ -121,7 +184,7 @@ func (sts *MemoryTopicStore) Set(item *Topic) error {
return nil return nil
} }
func (sts *MemoryTopicStore) Add(item *Topic) error { func (sts *MemoryTopicStore) CacheAdd(item *Topic) error {
if sts.length >= sts.capacity { if sts.length >= sts.capacity {
return ErrStoreCapacityOverflow return ErrStoreCapacityOverflow
} }
@ -132,7 +195,8 @@ func (sts *MemoryTopicStore) Add(item *Topic) error {
return nil return nil
} }
func (sts *MemoryTopicStore) AddUnsafe(item *Topic) error { // TODO: Make these length increments thread-safe. Ditto for the other DataStores
func (sts *MemoryTopicStore) CacheAddUnsafe(item *Topic) error {
if sts.length >= sts.capacity { if sts.length >= sts.capacity {
return ErrStoreCapacityOverflow return ErrStoreCapacityOverflow
} }
@ -141,7 +205,8 @@ func (sts *MemoryTopicStore) AddUnsafe(item *Topic) error {
return nil return nil
} }
func (sts *MemoryTopicStore) Remove(id int) error { // TODO: Make these length decrements thread-safe. Ditto for the other DataStores
func (sts *MemoryTopicStore) CacheRemove(id int) error {
sts.Lock() sts.Lock()
delete(sts.items, id) delete(sts.items, id)
sts.Unlock() sts.Unlock()
@ -149,12 +214,13 @@ func (sts *MemoryTopicStore) Remove(id int) error {
return nil return nil
} }
func (sts *MemoryTopicStore) RemoveUnsafe(id int) error { func (sts *MemoryTopicStore) CacheRemoveUnsafe(id int) error {
delete(sts.items, id) delete(sts.items, id)
sts.length-- sts.length--
return nil return nil
} }
// ? - What is this? Do we need it? Should it be in the main store interface?
func (sts *MemoryTopicStore) AddLastTopic(item *Topic, fid int) error { func (sts *MemoryTopicStore) AddLastTopic(item *Topic, fid int) error {
// Coming Soon... // Coming Soon...
return nil return nil
@ -172,16 +238,40 @@ func (sts *MemoryTopicStore) GetCapacity() int {
return sts.capacity return sts.capacity
} }
// Return the total number of topics on these forums
func (sts *MemoryTopicStore) GetGlobalCount() int {
var tcount int
err := sts.topicCount.QueryRow().Scan(&tcount)
if err != nil {
LogError(err)
}
return tcount
}
type SQLTopicStore struct { type SQLTopicStore struct {
get *sql.Stmt get *sql.Stmt
exists *sql.Stmt
topicCount *sql.Stmt
} }
func NewSQLTopicStore() *SQLTopicStore { func NewSQLTopicStore() *SQLTopicStore {
stmt, err := qgen.Builder.SimpleSelect("topics", "title, content, createdBy, createdAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data", "tid = ?", "", "") getStmt, err := qgen.Builder.SimpleSelect("topics", "title, content, createdBy, createdAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data", "tid = ?", "", "")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
return &SQLTopicStore{stmt} existsStmt, err := qgen.Builder.SimpleSelect("topics", "tid", "tid = ?", "", "")
if err != nil {
log.Fatal(err)
}
topicCountStmt, err := qgen.Builder.SimpleCount("topics", "", "")
if err != nil {
log.Fatal(err)
}
return &SQLTopicStore{
get: getStmt,
exists: existsStmt,
topicCount: topicCountStmt,
}
} }
func (sts *SQLTopicStore) Get(id int) (*Topic, error) { func (sts *SQLTopicStore) Get(id int) (*Topic, error) {
@ -191,20 +281,6 @@ func (sts *SQLTopicStore) Get(id int) (*Topic, error) {
return &topic, err return &topic, err
} }
func (sts *SQLTopicStore) GetUnsafe(id int) (*Topic, error) {
topic := Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
return &topic, err
}
func (sts *SQLTopicStore) CascadeGet(id int) (*Topic, error) {
topic := Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
return &topic, err
}
func (sts *SQLTopicStore) BypassGet(id int) (*Topic, error) { func (sts *SQLTopicStore) BypassGet(id int) (*Topic, error) {
topic := &Topic{ID: id} topic := &Topic{ID: id}
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
@ -212,37 +288,52 @@ func (sts *SQLTopicStore) BypassGet(id int) (*Topic, error) {
return topic, err return topic, err
} }
func (sts *SQLTopicStore) Load(id int) error { func (sts *SQLTopicStore) Reload(id int) error {
topic := Topic{ID: id} return sts.exists.QueryRow(id).Scan(&id)
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) }
topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
func (sts *SQLTopicStore) Exists(id int) bool {
return sts.exists.QueryRow(id).Scan(&id) == nil
}
// TODO: Use a transaction here
func (sts *SQLTopicStore) Delete(id int) error {
topic, err := sts.Get(id)
if err != nil {
return nil // Already gone, maybe we should check for other errors here
}
topicCreator, err := users.Get(topic.CreatedBy)
if err == nil {
wcount := wordCount(topic.Content)
err = topicCreator.decreasePostStats(wcount, true)
if err != nil {
return err
}
} else if err != ErrNoRows {
return err
}
err = fstore.DecrementTopicCount(topic.ParentID)
if err != nil && err != ErrNoRows {
return err
}
_, err = delete_topic_stmt.Exec(id)
return err return err
} }
// Placeholder methods, the actual queries are done elsewhere
func (sts *SQLTopicStore) Set(item *Topic) error {
return nil
}
func (sts *SQLTopicStore) Add(item *Topic) error {
return nil
}
func (sts *SQLTopicStore) AddUnsafe(item *Topic) error {
return nil
}
func (sts *SQLTopicStore) Remove(id int) error {
return nil
}
func (sts *SQLTopicStore) RemoveUnsafe(id int) error {
return nil
}
func (sts *SQLTopicStore) AddLastTopic(item *Topic, fid int) error { func (sts *SQLTopicStore) AddLastTopic(item *Topic, fid int) error {
// Coming Soon... // Coming Soon...
return nil return nil
} }
func (sts *SQLTopicStore) GetCapacity() int {
return 0
}
func (sts *SQLTopicStore) GetLength() int { // Return the total number of topics on these forums
return 0 // Return the total number of topics on the forums? func (sts *SQLTopicStore) GetGlobalCount() int {
var tcount int
err := sts.topicCount.QueryRow().Scan(&tcount)
if err != nil {
LogError(err)
}
return tcount
} }

29
user.go
View File

@ -67,7 +67,7 @@ func (user *User) Unban() error {
if err != nil { if err != nil {
return err return err
} }
return users.Load(user.ID) return users.Reload(user.ID)
} }
// TODO: Use a transaction to avoid race conditions // TODO: Use a transaction to avoid race conditions
@ -87,7 +87,7 @@ func (user *User) ScheduleGroupUpdate(gid int, issuedBy int, duration time.Durat
if err != nil { if err != nil {
return err return err
} }
return users.Load(user.ID) return users.Reload(user.ID)
} }
// TODO: Use a transaction to avoid race conditions // TODO: Use a transaction to avoid race conditions
@ -100,7 +100,7 @@ func (user *User) RevertGroupUpdate() error {
if err != nil { if err != nil {
return err return err
} }
return users.Load(user.ID) return users.Reload(user.ID)
} }
func BcryptCheckPassword(realPassword string, password string, salt string) (err error) { func BcryptCheckPassword(realPassword string, password string, salt string) (err error) {
@ -250,22 +250,23 @@ func (user *User) decreasePostStats(wcount int, topic bool) error {
} }
func initUserPerms(user *User) { func initUserPerms(user *User) {
if user.IsSuperAdmin {
user.Perms = AllPerms
user.PluginPerms = AllPluginPerms
} else {
user.Perms = groups[user.Group].Perms
user.PluginPerms = groups[user.Group].PluginPerms
}
if user.TempGroup != 0 { if user.TempGroup != 0 {
user.Group = user.TempGroup user.Group = user.TempGroup
} }
user.IsAdmin = user.IsSuperAdmin || groups[user.Group].IsAdmin group := gstore.DirtyGet(user.Group)
user.IsSuperMod = user.IsAdmin || groups[user.Group].IsMod if user.IsSuperAdmin {
user.Perms = AllPerms
user.PluginPerms = AllPluginPerms
} else {
user.Perms = group.Perms
user.PluginPerms = group.PluginPerms
}
user.IsAdmin = user.IsSuperAdmin || group.IsAdmin
user.IsSuperMod = user.IsAdmin || group.IsMod
user.IsMod = user.IsSuperMod user.IsMod = user.IsSuperMod
user.IsBanned = groups[user.Group].IsBanned user.IsBanned = group.IsBanned
if user.IsBanned && user.IsSuperMod { if user.IsBanned && user.IsSuperMod {
user.IsBanned = false user.IsBanned = false
} }

View File

@ -13,33 +13,40 @@ import (
) )
// TODO: Add the watchdog goroutine // TODO: Add the watchdog goroutine
// TODO: Add some sort of update method
var users UserStore var users UserStore
var errAccountExists = errors.New("this username is already in use") var errAccountExists = errors.New("this username is already in use")
type UserStore interface { type UserStore interface {
Load(id int) error Reload(id int) error // ? - Should we move this to TopicCache? Might require us to do a lot more casting in Gosora though...
Get(id int) (*User, error) Get(id int) (*User, error)
GetUnsafe(id int) (*User, error) Exists(id int) bool
CascadeGet(id int) (*User, error) //BulkGet(ids []int) ([]*User, error)
//BulkCascadeGet(ids []int) ([]*User, error) BulkGetMap(ids []int) (map[int]*User, error)
BulkCascadeGetMap(ids []int) (map[int]*User, error)
BypassGet(id int) (*User, error) BypassGet(id int) (*User, error)
Set(item *User) error Create(username string, password string, email string, group int, active int) (int, error)
Add(item *User) error
AddUnsafe(item *User) error
Remove(id int) error
RemoveUnsafe(id int) error
CreateUser(username string, password string, email string, group int, active int) (int, error)
GetLength() int
GetCapacity() int
GetGlobalCount() int GetGlobalCount() int
} }
type UserCache interface {
CacheGet(id int) (*User, error)
CacheGetUnsafe(id int) (*User, error)
CacheSet(item *User) error
CacheAdd(item *User) error
CacheAddUnsafe(item *User) error
CacheRemove(id int) error
CacheRemoveUnsafe(id int) error
GetLength() int
SetCapacity(capacity int)
GetCapacity() int
}
type MemoryUserStore struct { type MemoryUserStore struct {
items map[int]*User items map[int]*User
length int length int
capacity int capacity int
get *sql.Stmt get *sql.Stmt
exists *sql.Stmt
register *sql.Stmt register *sql.Stmt
usernameExists *sql.Stmt usernameExists *sql.Stmt
userCount *sql.Stmt userCount *sql.Stmt
@ -53,6 +60,11 @@ func NewMemoryUserStore(capacity int) *MemoryUserStore {
log.Fatal(err) log.Fatal(err)
} }
existsStmt, err := qgen.Builder.SimpleSelect("users", "uid", "uid = ?", "", "")
if err != nil {
log.Fatal(err)
}
// Add an admin version of register_stmt with more flexibility? // Add an admin version of register_stmt with more flexibility?
// create_account_stmt, err = db.Prepare("INSERT INTO // create_account_stmt, err = db.Prepare("INSERT INTO
registerStmt, err := qgen.Builder.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message", "?,?,?,?,?,0,'',?,''") registerStmt, err := qgen.Builder.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message", "?,?,?,?,?,0,'',?,''")
@ -74,13 +86,14 @@ func NewMemoryUserStore(capacity int) *MemoryUserStore {
items: make(map[int]*User), items: make(map[int]*User),
capacity: capacity, capacity: capacity,
get: getStmt, get: getStmt,
exists: existsStmt,
register: registerStmt, register: registerStmt,
usernameExists: usernameExistsStmt, usernameExists: usernameExistsStmt,
userCount: userCountStmt, userCount: userCountStmt,
} }
} }
func (sus *MemoryUserStore) Get(id int) (*User, error) { func (sus *MemoryUserStore) CacheGet(id int) (*User, error) {
sus.RLock() sus.RLock()
item, ok := sus.items[id] item, ok := sus.items[id]
sus.RUnlock() sus.RUnlock()
@ -90,7 +103,7 @@ func (sus *MemoryUserStore) Get(id int) (*User, error) {
return item, ErrNoRows return item, ErrNoRows
} }
func (sus *MemoryUserStore) GetUnsafe(id int) (*User, error) { func (sus *MemoryUserStore) CacheGetUnsafe(id int) (*User, error) {
item, ok := sus.items[id] item, ok := sus.items[id]
if ok { if ok {
return item, nil return item, nil
@ -98,7 +111,7 @@ func (sus *MemoryUserStore) GetUnsafe(id int) (*User, error) {
return item, ErrNoRows return item, ErrNoRows
} }
func (sus *MemoryUserStore) CascadeGet(id int) (*User, error) { func (sus *MemoryUserStore) Get(id int) (*User, error) {
sus.RLock() sus.RLock()
user, ok := sus.items[id] user, ok := sus.items[id]
sus.RUnlock() sus.RUnlock()
@ -117,10 +130,10 @@ func (sus *MemoryUserStore) CascadeGet(id int) (*User, error) {
user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1) user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1)
} }
user.Link = buildProfileURL(nameToSlug(user.Name), id) user.Link = buildProfileURL(nameToSlug(user.Name), id)
user.Tag = groups[user.Group].Tag user.Tag = gstore.DirtyGet(user.Group).Tag
initUserPerms(user) initUserPerms(user)
if err == nil { if err == nil {
sus.Set(user) sus.CacheSet(user)
} }
return user, err return user, err
} }
@ -138,7 +151,7 @@ func (sus *MemoryUserStore) bulkGet(ids []int) (list []*User) {
// TODO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts? // TODO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts?
// TODO: ID of 0 should always error? // TODO: ID of 0 should always error?
func (sus *MemoryUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, err error) { func (sus *MemoryUserStore) BulkGetMap(ids []int) (list map[int]*User, err error) {
var idCount = len(ids) var idCount = len(ids)
list = make(map[int]*User) list = make(map[int]*User)
if idCount == 0 { if idCount == 0 {
@ -195,11 +208,11 @@ func (sus *MemoryUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, er
user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1) user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1)
} }
user.Link = buildProfileURL(nameToSlug(user.Name), user.ID) user.Link = buildProfileURL(nameToSlug(user.Name), user.ID)
user.Tag = groups[user.Group].Tag user.Tag = gstore.DirtyGet(user.Group).Tag
initUserPerms(user) initUserPerms(user)
// Add it to the cache... // Add it to the cache...
_ = sus.Set(user) _ = sus.CacheSet(user)
// Add it to the list to be returned // Add it to the list to be returned
list[user.ID] = user list[user.ID] = user
@ -245,16 +258,16 @@ func (sus *MemoryUserStore) BypassGet(id int) (*User, error) {
user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1) user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1)
} }
user.Link = buildProfileURL(nameToSlug(user.Name), id) user.Link = buildProfileURL(nameToSlug(user.Name), id)
user.Tag = groups[user.Group].Tag user.Tag = gstore.DirtyGet(user.Group).Tag
initUserPerms(user) initUserPerms(user)
return user, err return user, err
} }
func (sus *MemoryUserStore) Load(id int) error { func (sus *MemoryUserStore) Reload(id int) error {
user := &User{ID: id, Loggedin: true} user := &User{ID: id, Loggedin: true}
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup) err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
if err != nil { if err != nil {
sus.Remove(id) sus.CacheRemove(id)
return err return err
} }
@ -266,13 +279,17 @@ func (sus *MemoryUserStore) Load(id int) error {
user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1) user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1)
} }
user.Link = buildProfileURL(nameToSlug(user.Name), id) user.Link = buildProfileURL(nameToSlug(user.Name), id)
user.Tag = groups[user.Group].Tag user.Tag = gstore.DirtyGet(user.Group).Tag
initUserPerms(user) initUserPerms(user)
_ = sus.Set(user) _ = sus.CacheSet(user)
return nil return nil
} }
func (sus *MemoryUserStore) Set(item *User) error { func (sus *MemoryUserStore) Exists(id int) bool {
return sus.exists.QueryRow(id).Scan(&id) == nil
}
func (sus *MemoryUserStore) CacheSet(item *User) error {
sus.Lock() sus.Lock()
user, ok := sus.items[item.ID] user, ok := sus.items[item.ID]
if ok { if ok {
@ -289,7 +306,7 @@ func (sus *MemoryUserStore) Set(item *User) error {
return nil return nil
} }
func (sus *MemoryUserStore) Add(item *User) error { func (sus *MemoryUserStore) CacheAdd(item *User) error {
if sus.length >= sus.capacity { if sus.length >= sus.capacity {
return ErrStoreCapacityOverflow return ErrStoreCapacityOverflow
} }
@ -300,7 +317,7 @@ func (sus *MemoryUserStore) Add(item *User) error {
return nil return nil
} }
func (sus *MemoryUserStore) AddUnsafe(item *User) error { func (sus *MemoryUserStore) CacheAddUnsafe(item *User) error {
if sus.length >= sus.capacity { if sus.length >= sus.capacity {
return ErrStoreCapacityOverflow return ErrStoreCapacityOverflow
} }
@ -309,7 +326,7 @@ func (sus *MemoryUserStore) AddUnsafe(item *User) error {
return nil return nil
} }
func (sus *MemoryUserStore) Remove(id int) error { func (sus *MemoryUserStore) CacheRemove(id int) error {
sus.Lock() sus.Lock()
delete(sus.items, id) delete(sus.items, id)
sus.Unlock() sus.Unlock()
@ -317,13 +334,13 @@ func (sus *MemoryUserStore) Remove(id int) error {
return nil return nil
} }
func (sus *MemoryUserStore) RemoveUnsafe(id int) error { func (sus *MemoryUserStore) CacheRemoveUnsafe(id int) error {
delete(sus.items, id) delete(sus.items, id)
sus.length-- sus.length--
return nil return nil
} }
func (sus *MemoryUserStore) CreateUser(username string, password string, email string, group int, active int) (int, error) { func (sus *MemoryUserStore) Create(username string, password string, email string, group int, active int) (int, error) {
// Is this username already taken..? // Is this username already taken..?
err := sus.usernameExists.QueryRow(username).Scan(&username) err := sus.usernameExists.QueryRow(username).Scan(&username)
if err != ErrNoRows { if err != ErrNoRows {
@ -373,6 +390,7 @@ func (sus *MemoryUserStore) GetGlobalCount() int {
type SQLUserStore struct { type SQLUserStore struct {
get *sql.Stmt get *sql.Stmt
exists *sql.Stmt
register *sql.Stmt register *sql.Stmt
usernameExists *sql.Stmt usernameExists *sql.Stmt
userCount *sql.Stmt userCount *sql.Stmt
@ -384,6 +402,11 @@ func NewSQLUserStore() *SQLUserStore {
log.Fatal(err) log.Fatal(err)
} }
existsStmt, err := qgen.Builder.SimpleSelect("users", "uid", "uid = ?", "", "")
if err != nil {
log.Fatal(err)
}
// Add an admin version of register_stmt with more flexibility? // Add an admin version of register_stmt with more flexibility?
// create_account_stmt, err = db.Prepare("INSERT INTO // create_account_stmt, err = db.Prepare("INSERT INTO
registerStmt, err := qgen.Builder.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message", "?,?,?,?,?,0,'',?,''") registerStmt, err := qgen.Builder.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message", "?,?,?,?,?,0,'',?,''")
@ -403,6 +426,7 @@ func NewSQLUserStore() *SQLUserStore {
return &SQLUserStore{ return &SQLUserStore{
get: getStmt, get: getStmt,
exists: existsStmt,
register: registerStmt, register: registerStmt,
usernameExists: usernameExistsStmt, usernameExists: usernameExistsStmt,
userCount: userCountStmt, userCount: userCountStmt,
@ -421,47 +445,13 @@ func (sus *SQLUserStore) Get(id int) (*User, error) {
user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1) user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1)
} }
user.Link = buildProfileURL(nameToSlug(user.Name), id) user.Link = buildProfileURL(nameToSlug(user.Name), id)
user.Tag = groups[user.Group].Tag user.Tag = gstore.DirtyGet(user.Group).Tag
initUserPerms(&user)
return &user, err
}
func (sus *SQLUserStore) GetUnsafe(id int) (*User, error) {
user := User{ID: id, Loggedin: true}
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
if user.Avatar != "" {
if user.Avatar[0] == '.' {
user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar
}
} else {
user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1)
}
user.Link = buildProfileURL(nameToSlug(user.Name), id)
user.Tag = groups[user.Group].Tag
initUserPerms(&user)
return &user, err
}
func (sus *SQLUserStore) CascadeGet(id int) (*User, error) {
user := User{ID: id, Loggedin: true}
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
if user.Avatar != "" {
if user.Avatar[0] == '.' {
user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar
}
} else {
user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1)
}
user.Link = buildProfileURL(nameToSlug(user.Name), id)
user.Tag = groups[user.Group].Tag
initUserPerms(&user) initUserPerms(&user)
return &user, err return &user, err
} }
// TODO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts? // TODO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts?
func (sus *SQLUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, err error) { func (sus *SQLUserStore) BulkGetMap(ids []int) (list map[int]*User, err error) {
var qlist string var qlist string
var uidList []interface{} var uidList []interface{}
for _, id := range ids { for _, id := range ids {
@ -497,7 +487,7 @@ func (sus *SQLUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, err e
user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1) user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1)
} }
user.Link = buildProfileURL(nameToSlug(user.Name), user.ID) user.Link = buildProfileURL(nameToSlug(user.Name), user.ID)
user.Tag = groups[user.Group].Tag user.Tag = gstore.DirtyGet(user.Group).Tag
initUserPerms(user) initUserPerms(user)
// Add it to the list to be returned // Add it to the list to be returned
@ -519,18 +509,20 @@ func (sus *SQLUserStore) BypassGet(id int) (*User, error) {
user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1) user.Avatar = strings.Replace(config.Noavatar, "{id}", strconv.Itoa(user.ID), 1)
} }
user.Link = buildProfileURL(nameToSlug(user.Name), id) user.Link = buildProfileURL(nameToSlug(user.Name), id)
user.Tag = groups[user.Group].Tag user.Tag = gstore.DirtyGet(user.Group).Tag
initUserPerms(&user) initUserPerms(&user)
return &user, err return &user, err
} }
func (sus *SQLUserStore) Load(id int) error { func (sus *SQLUserStore) Reload(id int) error {
user := &User{ID: id} return sus.exists.QueryRow(id).Scan(&id)
// Simplify this into a quick check to see whether the user exists. Add an Exists method to facilitate this?
return sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
} }
func (sus *SQLUserStore) CreateUser(username string, password string, email string, group int, active int) (int, error) { func (sus *SQLUserStore) Exists(id int) bool {
return sus.exists.QueryRow(id).Scan(&id) == nil
}
func (sus *SQLUserStore) Create(username string, password string, email string, group int, active int) (int, error) {
// Is this username already taken..? // Is this username already taken..?
err := sus.usernameExists.QueryRow(username).Scan(&username) err := sus.usernameExists.QueryRow(username).Scan(&username)
if err != ErrNoRows { if err != ErrNoRows {
@ -556,35 +548,7 @@ func (sus *SQLUserStore) CreateUser(username string, password string, email stri
return int(lastID), err return int(lastID), err
} }
// Placeholder methods, as we're not don't need to do any cache management with this implementation ofr the UserStore
func (sus *SQLUserStore) Set(item *User) error {
return nil
}
func (sus *SQLUserStore) Add(item *User) error {
return nil
}
func (sus *SQLUserStore) AddUnsafe(item *User) error {
return nil
}
func (sus *SQLUserStore) Remove(id int) error {
return nil
}
func (sus *SQLUserStore) RemoveUnsafe(id int) error {
return nil
}
func (sus *SQLUserStore) GetCapacity() int {
return 0
}
// Return the total number of users registered on the forums // Return the total number of users registered on the forums
func (sus *SQLUserStore) GetLength() int {
var ucount int
err := sus.userCount.QueryRow().Scan(&ucount)
if err != nil {
LogError(err)
}
return ucount
}
func (sus *SQLUserStore) GetGlobalCount() int { func (sus *SQLUserStore) GetGlobalCount() int {
var ucount int var ucount int
err := sus.userCount.QueryRow().Scan(&ucount) err := sus.userCount.QueryRow().Scan(&ucount)

View File

@ -386,13 +386,6 @@ func getLevels(maxLevel int) []float64 {
return out return out
} }
func fillGroupIDGap(biggerID int, smallerID int) {
dummy := Group{ID: 0, Name: ""}
for i := smallerID; i > biggerID; i++ {
groups = append(groups, dummy)
}
}
func buildSlug(slug string, id int) string { func buildSlug(slug string, id int) string {
if slug == "" { if slug == "" {
return strconv.Itoa(id) return strconv.Itoa(id)

View File

@ -168,7 +168,7 @@ func routeWebsockets(w http.ResponseWriter, r *http.Request, user User) {
if err != nil { if err != nil {
return return
} }
userptr, err := users.CascadeGet(user.ID) userptr, err := users.Get(user.ID)
if err != nil && err != ErrStoreCapacityOverflow { if err != nil && err != ErrStoreCapacityOverflow {
return return
} }