From 2557eb935b3ff66ebdd6fbbda32ee770f88133c3 Mon Sep 17 00:00:00 2001 From: Azareal Date: Fri, 15 Sep 2017 23:20:01 +0100 Subject: [PATCH] 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. --- README.md | 2 +- alerts.go | 10 +- auth.go | 6 +- cache.go | 18 +- database.go | 52 +----- forum_store.go | 66 +++---- general_test.go | 14 +- group.go | 104 +---------- group_store.go | 204 ++++++++++++++++++++- main.go | 2 - member_routes.go | 36 ++-- misc_test.go | 216 +++++++++++++++++++--- mod_routes.go | 55 ++---- pages.go | 6 +- panel_routes.go | 131 +++++++++----- permissions.go | 61 ++++--- plugin_socialgroups.go | 2 +- reply.go | 2 +- routes.go | 59 ++++-- routes_common.go | 32 +++- tasks.go | 2 +- template_list.go | 90 +++++----- template_profile.go | 101 +++++------ templates/profile.html | 7 +- themes/tempra-conflux/public/main.css | 49 +++-- themes/tempra-conflux/public/panel.css | 2 +- themes/tempra-simple/public/main.css | 8 +- topic.go | 25 +-- topic_store.go | 239 +++++++++++++++++-------- user.go | 29 +-- user_store.go | 170 +++++++----------- utils.go | 7 - websockets.go | 2 +- 33 files changed, 1106 insertions(+), 703 deletions(-) diff --git a/README.md b/README.md index 760971bc..3d5d78f3 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Other modern features like alerts, likes, advanced dashboard with live stats (CP # 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. diff --git a/alerts.go b/alerts.go index 224dba90..902e076d 100644 --- a/alerts.go +++ b/alerts.go @@ -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) { var targetUser *User - actor, err := users.CascadeGet(actorID) + actor, err := users.Get(actorID) if err != nil { return "", errors.New("Unable to find the actor") } /*if elementType != "forum" { - targetUser, err = users.CascadeGet(targetUser_id) + targetUser, err = users.Get(targetUser_id) if err != nil { LocalErrorJS("Unable to find the target user",w,r) return @@ -52,7 +52,7 @@ func buildAlert(asid int, event string, elementType string, actorID int, targetU case "forum": if event == "reply" { act = "created a new topic" - topic, err := topics.CascadeGet(elementID) + topic, err := topics.Get(elementID) if err != nil { 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" } case "topic": - topic, err := topics.CascadeGet(elementID) + topic, err := topics.Get(elementID) if err != nil { 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" } case "user": - targetUser, err = users.CascadeGet(elementID) + targetUser, err = users.Get(elementID) if err != nil { return "", errors.New("Unable to find the target user") } diff --git a/auth.go b/auth.go index dbd9862a..dd6388e9 100644 --- a/auth.go +++ b/auth.go @@ -93,7 +93,7 @@ func (auth *DefaultAuth) ForceLogout(uid int) error { } // Flush the user out of the cache and reload - err = users.Load(uid) + err = users.Reload(uid) if err != nil { 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..? - user, err = users.CascadeGet(uid) + user, err = users.Get(uid) if err == ErrNoRows { return &guestUser, false } else if err != nil { @@ -168,6 +168,6 @@ func (auth *DefaultAuth) CreateSession(uid int) (session string, err error) { } // Reload the user data - _ = users.Load(uid) + _ = users.Reload(uid) return session, nil } diff --git a/cache.go b/cache.go index 9f85944b..f5a0c397 100644 --- a/cache.go +++ b/cache.go @@ -18,14 +18,18 @@ var ErrStoreCapacityOverflow = errors.New("This datastore has reached it's maxim type DataStore interface { Load(id int) error Get(id int) (interface{}, error) - GetUnsafe(id int) (interface{}, error) - CascadeGet(id int) (interface{}, error) BypassGet(id int) (interface{}, error) - Set(item interface{}) error - Add(item interface{}) error - AddUnsafe(item interface{}) error - Remove(id int) error - RemoveUnsafe(id int) error + //GetGlobalCount() +} + +type DataCache interface { + 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 GetCapacity() int } diff --git a/database.go b/database.go index e52ac8f8..4c52352f 100644 --- a/database.go +++ b/database.go @@ -1,7 +1,7 @@ package main import "log" -import "encoding/json" + import "database/sql" var db *sql.DB @@ -19,57 +19,11 @@ func initDatabase() (err error) { } log.Print("Loading the usergroups.") - groups = append(groups, Group{ID: 0, Name: "System"}) - - rows, err := get_groups_stmt.Query() + gstore = NewMemoryGroupStore() + err = gstore.LoadGroups() 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 - } - - // 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.") fstore = NewMemoryForumStore() diff --git a/forum_store.go b/forum_store.go index 6b868e40..4ee7a15a 100644 --- a/forum_store.go +++ b/forum_store.go @@ -17,7 +17,7 @@ import ( var forumUpdateMutex 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 // ForumStore is an interface for accessing the forums and the metadata stored on them @@ -25,15 +25,11 @@ type ForumStore interface { LoadForums() error DirtyGet(id int) *Forum Get(id int) (*Forum, error) - CascadeGet(id int) (*Forum, error) - CascadeGetCopy(id int) (Forum, error) + GetCopy(id int) (Forum, error) BypassGet(id int) (*Forum, error) - Load(id int) error - Set(forum *Forum) error + Reload(id int) error // ? - Should we move this to TopicCache? Might require us to do a lot more casting in Gosora though... //Update(Forum) error - //CascadeUpdate(Forum) error - Delete(id int) - CascadeDelete(id int) error + Delete(id int) error IncrementTopicCount(id int) error DecrementTopicCount(id int) error UpdateLastTopic(topicName string, tid int, username string, uid int, time string, fid int) error @@ -44,11 +40,17 @@ type ForumStore interface { GetAllVisibleIDs() ([]int, error) //GetChildren(parentID int, parentType string) ([]*Forum,error) //GetFirstChild(parentID int, parentType string) (*Forum,error) - CreateForum(forumName string, forumDesc string, active bool, preset string) (int, error) + Create(forumName string, forumDesc string, active bool, preset string) (int, error) 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 type MemoryForumStore struct { forums sync.Map // map[int]*Forum @@ -158,39 +160,38 @@ func (mfs *MemoryForumStore) DirtyGet(id int) *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) - forum := fint.(*Forum) - if !ok || forum.Name == "" { + if !ok || fint.(*Forum).Name == "" { 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) - forum := fint.(*Forum) - if !ok || forum.Name == "" { + if !ok || fint.(*Forum).Name == "" { + var forum = &Forum{ID: id} err := mfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime) forum.Link = buildForumURL(nameToSlug(forum.Name), forum.ID) forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID) return forum, err } - return 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) - forum := fint.(*Forum) - if !ok || forum.Name == "" { + if !ok || fint.(*Forum).Name == "" { + var forum = Forum{ID: id} err := mfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime) forum.Link = buildForumURL(nameToSlug(forum.Name), forum.ID) forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID) - return *forum, err + return forum, err } - return *forum, nil + return *fint.(*Forum), nil } func (mfs *MemoryForumStore) BypassGet(id int) (*Forum, error) { @@ -202,7 +203,7 @@ func (mfs *MemoryForumStore) BypassGet(id int) (*Forum, error) { return &forum, err } -func (mfs *MemoryForumStore) Load(id int) error { +func (mfs *MemoryForumStore) Reload(id int) error { var forum = Forum{ID: id} err := mfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime) if err != nil { @@ -211,11 +212,11 @@ func (mfs *MemoryForumStore) Load(id int) error { forum.Link = buildForumURL(nameToSlug(forum.Name), forum.ID) forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID) - mfs.Set(&forum) + mfs.CacheSet(&forum) return nil } -func (mfs *MemoryForumStore) Set(forum *Forum) error { +func (mfs *MemoryForumStore) CacheSet(forum *Forum) error { if !mfs.Exists(forum.ID) { return ErrNoRows } @@ -261,30 +262,31 @@ func (mfs *MemoryForumStore) GetFirstChild(parentID int, parentType string) (*Fo return nil, nil }*/ +// TODO: Add a query for this rather than hitting cache func (mfs *MemoryForumStore) Exists(id int) bool { forum, ok := mfs.forums.Load(id) return ok && forum.(*Forum).Name != "" } // 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.rebuildView() } -func (mfs *MemoryForumStore) CascadeDelete(id int) error { +func (mfs *MemoryForumStore) Delete(id int) error { forumUpdateMutex.Lock() defer forumUpdateMutex.Unlock() _, err := mfs.delete.Exec(id) if err != nil { return err } - mfs.Delete(id) + mfs.CacheDelete(id) return nil } func (mfs *MemoryForumStore) IncrementTopicCount(id int) error { - forum, err := mfs.CascadeGet(id) + forum, err := mfs.Get(id) if err != nil { return err } @@ -297,7 +299,7 @@ func (mfs *MemoryForumStore) IncrementTopicCount(id int) error { } func (mfs *MemoryForumStore) DecrementTopicCount(id int) error { - forum, err := mfs.CascadeGet(id) + forum, err := mfs.Get(id) if err != nil { 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 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 { return err } @@ -330,7 +332,7 @@ func (mfs *MemoryForumStore) UpdateLastTopic(topicName string, tid int, username 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() res, err := create_forum_stmt.Exec(forumName, forumDesc, active, preset) if err != nil { diff --git a/general_test.go b/general_test.go index dae0b06d..818c7c80 100644 --- a/general_test.go +++ b/general_test.go @@ -100,7 +100,7 @@ func BenchmarkTopicAdminRouteParallel(b *testing.B) { } b.RunParallel(func(pb *testing.PB) { - admin, err := users.CascadeGet(1) + admin, err := users.Get(1) if err != nil { b.Fatal(err) } @@ -157,7 +157,7 @@ func BenchmarkForumsAdminRouteParallel(b *testing.B) { } b.RunParallel(func(pb *testing.PB) { - admin, err := users.CascadeGet(1) + admin, err := users.Get(1) if err != nil { panic(err) } @@ -188,7 +188,7 @@ func BenchmarkForumsAdminRouteParallelProf(b *testing.B) { } b.RunParallel(func(pb *testing.PB) { - admin, err := users.CascadeGet(1) + admin, err := users.Get(1) if err != nil { panic(err) } @@ -237,7 +237,7 @@ func BenchmarkForumsGuestRouteParallel(b *testing.B) { /*func BenchmarkRoutesSerial(b *testing.B) { b.ReportAllocs() - admin, err := users.CascadeGet(1) + admin, err := users.Get(1) if err != nil { panic(err) } @@ -1125,7 +1125,7 @@ func TestStaticRoute(t *testing.T) { init_plugins() } - admin, err := users.CascadeGet(1) + admin, err := users.Get(1) if err != nil { panic(err) } @@ -1181,7 +1181,7 @@ func TestForumsAdminRoute(t *testing.T) { init_plugins() } - admin, err := users.CascadeGet(1) + admin, err := users.Get(1) if err != nil { t.Fatal(err) } @@ -1231,7 +1231,7 @@ func TestForumsGuestRoute(t *testing.T) { init_plugins() } - admin, err := users.CascadeGet(1) + admin, err := users.Get(1) if err != nil { panic(err) } diff --git a/group.go b/group.go index e9c4c6b0..799c4bbd 100644 --- a/group.go +++ b/group.go @@ -1,9 +1,6 @@ package main -import "sync" -import "encoding/json" - -var groupUpdateMutex sync.Mutex +var blankGroup = Group{ID: 0, Name: ""} type GroupAdmin struct { ID int @@ -28,102 +25,3 @@ type Group struct { Forums []ForumPerms 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 != "" -} diff --git a/group_store.go b/group_store.go index 9ae4f57a..70d9340d 100644 --- a/group_store.go +++ b/group_store.go @@ -1,3 +1,205 @@ +/* Under Heavy Construction */ 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 +} diff --git a/main.go b/main.go index ab91e574..5f49027d 100644 --- a/main.go +++ b/main.go @@ -38,8 +38,6 @@ var startTime time.Time var externalSites = map[string]string{ "YT": "https://www.youtube.com/", } -var groups []Group -var groupCapCount int var staticFiles = make(map[string]SFile) var logWriter = io.MultiWriter(os.Stderr) diff --git a/member_routes.go b/member_routes.go index 4639f00d..763985b1 100644 --- a/member_routes.go +++ b/member_routes.go @@ -13,10 +13,9 @@ import ( "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 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? 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 { return } - if !user.Loggedin || !user.Perms.CreateTopic { + if !user.Perms.ViewTopic || !user.Perms.CreateTopic { NoPermissions(w, r, user) return } @@ -58,7 +57,12 @@ func routeTopicCreate(w http.ResponseWriter, r *http.Request, user User, sfid st return } } 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 } @@ -110,7 +114,7 @@ func routeTopicCreateSubmit(w http.ResponseWriter, r *http.Request, user User) { if !ok { return } - if !user.Loggedin || !user.Perms.CreateTopic { + if !user.Perms.ViewTopic || !user.Perms.CreateTopic { NoPermissions(w, r, user) return } @@ -172,7 +176,7 @@ func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) { return } - topic, err := topics.CascadeGet(tid) + topic, err := topics.Get(tid) if err == ErrNoRows { PreError("Couldn't find the parent topic", w, r) return @@ -186,7 +190,7 @@ func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) { if !ok { return } - if !user.Loggedin || !user.Perms.CreateReply { + if !user.Perms.ViewTopic || !user.Perms.CreateReply { NoPermissions(w, r, user) return } @@ -239,7 +243,7 @@ func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) { } // Reload the topic... - err = topics.Load(tid) + err = topics.Reload(tid) if err != nil && err == ErrNoRows { LocalError("The destination no longer exists", w, r, user) return @@ -269,7 +273,7 @@ func routeLikeTopic(w http.ResponseWriter, r *http.Request, user User) { return } - topic, err := topics.CascadeGet(tid) + topic, err := topics.Get(tid) if err == ErrNoRows { PreError("The requested topic doesn't exist.", w, r) return @@ -302,7 +306,7 @@ func routeLikeTopic(w http.ResponseWriter, r *http.Request, user User) { return } - _, err = users.CascadeGet(topic.CreatedBy) + _, err = users.Get(topic.CreatedBy) if err != nil && err == ErrNoRows { LocalError("The target user doesn't exist", w, r, user) 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) // Reload the topic... - err = topics.Load(tid) + err = topics.Reload(tid) if err != nil && err == ErrNoRows { LocalError("The liked topic no longer exists", w, r, user) return @@ -413,7 +417,7 @@ func routeReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user User) { return } - _, err = users.CascadeGet(reply.CreatedBy) + _, err = users.Get(reply.CreatedBy) if err != nil && err != ErrNoRows { LocalError("The target user doesn't exist", w, r, user) 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) { - if !user.Loggedin || !user.Perms.CreateReply { + if !user.Perms.ViewTopic || !user.Perms.CreateReply { NoPermissions(w, r, user) return } @@ -540,7 +544,7 @@ func routeReportSubmit(w http.ResponseWriter, r *http.Request, user User, sitemI return } - topic, err := topics.CascadeGet(reply.ParentID) + topic, err := topics.Get(reply.ParentID) if err == ErrNoRows { LocalError("We weren't able to find the topic the reported post is supposed to be in", w, r, user) return @@ -813,7 +817,7 @@ func routeAccountOwnEditAvatarSubmit(w http.ResponseWriter, r *http.Request, use return } user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + "." + ext - err = users.Load(user.ID) + err = users.Reload(user.ID) if err != nil { LocalError("This user no longer exists!", w, r, user) return @@ -871,7 +875,7 @@ func routeAccountOwnEditUsernameSubmit(w http.ResponseWriter, r *http.Request, u // TODO: Use the reloaded data instead for the name? user.Name = newUsername - err = users.Load(user.ID) + err = users.Reload(user.ID) if err != nil { LocalError("Your account doesn't exist!", w, r, user) return diff --git a/misc_test.go b/misc_test.go index 13f77b9c..7bd7b401 100644 --- a/misc_test.go +++ b/misc_test.go @@ -15,24 +15,30 @@ func TestUserStore(t *testing.T) { initPlugins() } + users = NewMemoryUserStore(config.UserCacheCapacity) + userStoreTest(t) + users = NewSQLUserStore() + userStoreTest(t) +} +func userStoreTest(t *testing.T) { var user *User var err error - _, err = users.CascadeGet(-1) + _, err = users.Get(-1) if err == nil { t.Error("UID #-1 shouldn't exist") } else if err != ErrNoRows { t.Fatal(err) } - _, err = users.CascadeGet(0) + _, err = users.Get(0) if err == nil { t.Error("UID #0 shouldn't exist") } else if err != ErrNoRows { t.Fatal(err) } - user, err = users.CascadeGet(1) + user, err = users.Get(1) if err == ErrNoRows { t.Error("Couldn't find UID #1") } 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? var userList map[int]*User - _, err = users.BulkCascadeGetMap([]int{-1}) - if err == nil { - t.Error("UID #-1 shouldn't exist") + userList, _ = users.BulkGetMap([]int{-1}) + if len(userList) > 0 { + t.Error("There shouldn't be any results for UID #-1") } - _, err = users.BulkCascadeGetMap([]int{0}) - if err == nil { - t.Error("UID #0 shouldn't exist") - } - - 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{0}) + if len(userList) > 0 { + t.Error("There shouldn't be any results for UID #0") } + userList, _ = users.BulkGetMap([]int{1}) 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 { - t.Error("Too many results were returned for UID #0") + t.Error("Too many results were returned for UID #1") } user, ok := userList[1] 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) } if user.ID != 1 { 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) { @@ -90,14 +180,14 @@ func TestForumStore(t *testing.T) { var forum *Forum var err error - _, err = fstore.CascadeGet(-1) + _, err = fstore.Get(-1) if err == nil { t.Error("FID #-1 shouldn't exist") } else if err != ErrNoRows { t.Fatal(err) } - forum, err = fstore.CascadeGet(0) + forum, err = fstore.Get(0) if err == ErrNoRows { t.Error("Couldn't find FID #0") } else if err != nil { @@ -111,7 +201,7 @@ func TestForumStore(t *testing.T) { t.Error("FID #0 is named '" + forum.Name + "' and not 'Uncategorised'") } - forum, err = fstore.CascadeGet(1) + forum, err = fstore.Get(1) if err == ErrNoRows { t.Error("Couldn't find FID #1") } else if err != nil { @@ -119,13 +209,13 @@ func TestForumStore(t *testing.T) { } 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" { t.Error("FID #0 is named '" + forum.Name + "' and not 'Reports'") } - forum, err = fstore.CascadeGet(2) + forum, err = fstore.Get(2) if err == ErrNoRows { t.Error("Couldn't find FID #2") } else if err != nil { @@ -133,6 +223,84 @@ func TestForumStore(t *testing.T) { } _ = 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) { diff --git a/mod_routes.go b/mod_routes.go index 379b19ed..6f8f8114 100644 --- a/mod_routes.go +++ b/mod_routes.go @@ -28,7 +28,7 @@ func routeEditTopic(w http.ResponseWriter, r *http.Request, user User) { return } - oldTopic, err := topics.CascadeGet(tid) + oldTopic, err := topics.Get(tid) if err == ErrNoRows { PreErrorJSQ("The topic you tried to edit doesn't exist.", w, r, isJs) return @@ -50,8 +50,9 @@ func routeEditTopic(w http.ResponseWriter, r *http.Request, user User) { topicName := r.PostFormValue("topic_name") topicStatus := r.PostFormValue("topic_status") isClosed := (topicStatus == "closed") - 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) if err != nil { 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 { LocalErrorJSQ("This topic no longer exists!", w, r, user, isJs) 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 func routeDeleteTopic(w http.ResponseWriter, r *http.Request, user User) { 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 } - topic, err := topics.CascadeGet(tid) + topic, err := topics.Get(tid) if err == ErrNoRows { PreError("The topic you tried to delete doesn't exist.", w, r) return @@ -137,7 +139,8 @@ func routeDeleteTopic(w http.ResponseWriter, r *http.Request, user User) { return } - _, err = delete_topic_stmt.Exec(tid) + // We might be able to handle this err better + err = topics.Delete(topic.CreatedBy) if err != nil { InternalError(err, w) return @@ -154,7 +157,7 @@ func routeDeleteTopic(w http.ResponseWriter, r *http.Request, user User) { 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) if err != nil { 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)) 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) { @@ -192,7 +175,7 @@ func routeStickTopic(w http.ResponseWriter, r *http.Request, user User) { return } - topic, err := topics.CascadeGet(tid) + topic, err := topics.Get(tid) if err == ErrNoRows { PreError("The topic you tried to pin doesn't exist.", w, r) return @@ -233,7 +216,7 @@ func routeStickTopic(w http.ResponseWriter, r *http.Request, user User) { return } - err = topics.Load(tid) + err = topics.Reload(tid) if err != nil { LocalError("This topic doesn't exist!", w, r, user) return @@ -248,7 +231,7 @@ func routeUnstickTopic(w http.ResponseWriter, r *http.Request, user User) { return } - topic, err := topics.CascadeGet(tid) + topic, err := topics.Get(tid) if err == ErrNoRows { PreError("The topic you tried to unpin doesn't exist.", w, r) return @@ -289,7 +272,7 @@ func routeUnstickTopic(w http.ResponseWriter, r *http.Request, user User) { return } - err = topics.Load(tid) + err = topics.Reload(tid) if err != nil { LocalError("This topic doesn't exist!", w, r, user) return @@ -411,7 +394,7 @@ func routeReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user User) { w.Write(successJSONBytes) } - replyCreator, err := users.CascadeGet(reply.CreatedBy) + replyCreator, err := users.Get(reply.CreatedBy) if err == nil { wcount := wordCount(reply.Content) err = replyCreator.decreasePostStats(wcount, false) @@ -439,7 +422,7 @@ func routeReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user User) { return } - err = topics.Load(reply.ParentID) + err = topics.Reload(reply.ParentID) if err != nil { LocalError("This topic no longer exists!", w, r, user) 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? - userList, err := users.BulkCascadeGetMap(idSlice) + userList, err := users.BulkGetMap(idSlice) if err != nil { InternalError(err, w) return @@ -693,7 +676,7 @@ func routeBanSubmit(w http.ResponseWriter, r *http.Request, user User) { return }*/ - targetUser, err := users.CascadeGet(uid) + targetUser, err := users.Get(uid) if err == ErrNoRows { LocalError("The user you're trying to ban no longer exists.", w, r, user) return @@ -784,7 +767,7 @@ func routeUnban(w http.ResponseWriter, r *http.Request, user User) { return } - targetUser, err := users.CascadeGet(uid) + targetUser, err := users.Get(uid) if err == ErrNoRows { LocalError("The user you're trying to unban no longer exists.", w, r, user) return @@ -874,7 +857,7 @@ func routeActivate(w http.ResponseWriter, r *http.Request, user User) { return } - err = users.Load(uid) + err = users.Reload(uid) if err != nil { LocalError("This user no longer exists!", w, r, user) return diff --git a/pages.go b/pages.go index 29ff778c..cd57ba56 100644 --- a/pages.go +++ b/pages.go @@ -190,7 +190,7 @@ type PanelEditGroupPage struct { } type GroupForumPermPreset struct { - Group Group + Group *Group Preset string } @@ -474,7 +474,7 @@ func parseMessage(msg string /*, user User*/) string { tid, intLen := coerceIntBytes(msgbytes[start:]) i += intLen - topic, err := topics.CascadeGet(tid) + topic, err := topics.Get(tid) if err != nil || !fstore.Exists(topic.ParentID) { outbytes = append(outbytes, invalidTopic...) lastItem = i @@ -550,7 +550,7 @@ func parseMessage(msg string /*, user User*/) string { uid, intLen := coerceIntBytes(msgbytes[start:]) i += intLen - menUser, err := users.CascadeGet(uid) + menUser, err := users.Get(uid) if err != nil { outbytes = append(outbytes, invalidProfile...) lastItem = i diff --git a/panel_routes.go b/panel_routes.go index 2defe9d4..ddaea3bd 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -244,7 +244,7 @@ func routePanelForumsCreateSubmit(w http.ResponseWriter, r *http.Request, user U factive := r.PostFormValue("forum-name") active := (factive == "on" || factive == "1") - _, err = fstore.CreateForum(fname, fdesc, active, fpreset) + _, err = fstore.Create(fname, fdesc, active, fpreset) if err != nil { InternalError(err, w) return @@ -274,7 +274,7 @@ func routePanelForumsDelete(w http.ResponseWriter, r *http.Request, user User, s return } - forum, err := fstore.CascadeGet(fid) + forum, err := fstore.Get(fid) if err == ErrNoRows { LocalError("The forum you're trying to delete doesn't exist.", w, r, user) return @@ -318,7 +318,7 @@ func routePanelForumsDeleteSubmit(w http.ResponseWriter, r *http.Request, user U return } - err = fstore.CascadeDelete(fid) + err = fstore.Delete(fid) if err == ErrNoRows { LocalError("The forum you're trying to delete doesn't exist.", w, r, user) return @@ -346,7 +346,7 @@ func routePanelForumsEdit(w http.ResponseWriter, r *http.Request, user User, sfi return } - forum, err := fstore.CascadeGet(fid) + forum, err := fstore.Get(fid) if err == ErrNoRows { LocalError("The forum you're trying to edit doesn't exist.", w, r, user) return @@ -359,7 +359,12 @@ func routePanelForumsEdit(w http.ResponseWriter, r *http.Request, user User, sfi forum.Preset = "custom" } - var glist = groups + glist, err := gstore.GetAll() + if err != nil { + InternalError(err, w) + return + } + var gplist []GroupForumPermPreset for gid, group := range glist { if gid == 0 { @@ -412,7 +417,7 @@ func routePanelForumsEditSubmit(w http.ResponseWriter, r *http.Request, user Use forumPreset := stripInvalidPreset(r.PostFormValue("forum_preset")) forumActive := r.PostFormValue("forum_active") - forum, err := fstore.CascadeGet(fid) + forum, err := fstore.Get(fid) if err == ErrNoRows { LocalErrorJSQ("The forum you're trying to edit doesn't exist.", w, r, user, isJs) return @@ -499,7 +504,7 @@ func routePanelForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, use permPreset := stripInvalidGroupForumPreset(r.PostFormValue("perm_preset")) fperms, changed := groupForumPresetToForumPerms(permPreset) - forum, err := fstore.CascadeGet(fid) + forum, err := fstore.Get(fid) if err == ErrNoRows { LocalErrorJSQ("This forum doesn't exist", w, r, user, isJs) return @@ -512,7 +517,13 @@ func routePanelForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, use defer forumUpdateMutex.Unlock() if changed { 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) if err != nil { @@ -525,7 +536,6 @@ func routePanelForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, use InternalErrorJSQ(err, w, r, isJs) return } - permUpdateMutex.Unlock() _, err = update_forum_stmt.Exec(forum.Name, forum.Desc, forum.Active, "", fid) 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) } - if groups[puser.Group].Tag != "" { - puser.Tag = groups[puser.Group].Tag + if gstore.DirtyGet(puser.Group).Tag != "" { + puser.Tag = gstore.DirtyGet(puser.Group).Tag } else { puser.Tag = "" } @@ -1202,7 +1212,7 @@ func routePanelUsersEdit(w http.ResponseWriter, r *http.Request, user User, suid return } - targetUser, err := users.CascadeGet(uid) + targetUser, err := users.Get(uid) if err == ErrNoRows { LocalError("The user you're trying to edit doesn't exist.", w, r, user) return @@ -1216,6 +1226,12 @@ func routePanelUsersEdit(w http.ResponseWriter, r *http.Request, user User, suid 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{} for _, group := range groups[1:] { if !user.Perms.EditUserGroupAdmin && group.IsAdmin { @@ -1259,7 +1275,7 @@ func routePanelUsersEditSubmit(w http.ResponseWriter, r *http.Request, user User return } - targetUser, err := users.CascadeGet(uid) + targetUser, err := users.Get(uid) if err == ErrNoRows { LocalError("The user you're trying to edit doesn't exist.", w, r, user) return @@ -1301,16 +1317,20 @@ func routePanelUsersEditSubmit(w http.ResponseWriter, r *http.Request, user User 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) 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) 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) return } @@ -1325,7 +1345,7 @@ func routePanelUsersEditSubmit(w http.ResponseWriter, r *http.Request, user User SetPassword(targetUser.ID, newpassword) } - err = users.Load(targetUser.ID) + err = users.Reload(targetUser.ID) if err != nil { LocalError("This user no longer exists!", w, r, user) return @@ -1349,7 +1369,8 @@ func routePanelGroups(w http.ResponseWriter, r *http.Request, user User) { var count int var groupList []GroupAdmin - for _, group := range groups[offset:] { + groups, _ := gstore.GetRange(offset, 0) + for _, group := range groups { if count == perPage { break } @@ -1412,13 +1433,16 @@ func routePanelGroupsEdit(w http.ResponseWriter, r *http.Request, user User, sgi return } - if !groupExists(gid) { + group, err := gstore.Get(gid) + if err == ErrNoRows { //log.Print("aaaaa monsters") NotFound(w, r) return + } else if err != nil { + InternalError(err, w) + return } - group := groups[gid] if group.IsAdmin && !user.Perms.EditGroupAdmin { LocalError("You need the EditGroupAdmin permission to edit an admin group.", w, r, user) return @@ -1471,13 +1495,16 @@ func routePanelGroupsEditPerms(w http.ResponseWriter, r *http.Request, user User return } - if !groupExists(gid) { + group, err := gstore.Get(gid) + if err == ErrNoRows { //log.Print("aaaaa monsters") NotFound(w, r) return + } else if err != nil { + InternalError(err, w) + return } - group := groups[gid] if group.IsAdmin && !user.Perms.EditGroupAdmin { LocalError("You need the EditGroupAdmin permission to edit an admin group.", w, r, user) return @@ -1554,13 +1581,16 @@ func routePanelGroupsEditSubmit(w http.ResponseWriter, r *http.Request, user Use return } - if !groupExists(gid) { + group, err := gstore.Get(gid) + if err == ErrNoRows { //log.Print("aaaaa monsters") NotFound(w, r) return + } else if err != nil { + InternalError(err, w) + return } - group := groups[gid] if group.IsAdmin && !user.Perms.EditGroupAdmin { LocalError("You need the EditGroupAdmin permission to edit an admin group.", w, r, user) return @@ -1611,9 +1641,9 @@ func routePanelGroupsEditSubmit(w http.ResponseWriter, r *http.Request, user Use InternalError(err, w) return } - groups[gid].IsAdmin = true - groups[gid].IsMod = true - groups[gid].IsBanned = false + group.IsAdmin = true + group.IsMod = true + group.IsBanned = false case "Mod": if !user.Perms.EditGroupSuperMod { 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) return } - groups[gid].IsAdmin = false - groups[gid].IsMod = true - groups[gid].IsBanned = false + group.IsAdmin = false + group.IsMod = true + group.IsBanned = false case "Banned": _, err = update_group_rank_stmt.Exec(0, 0, 1, gid) if err != nil { InternalError(err, w) return } - groups[gid].IsAdmin = false - groups[gid].IsMod = false - groups[gid].IsBanned = true + group.IsAdmin = false + group.IsMod = false + group.IsBanned = true case "Guest": LocalError("You can't designate a group as a guest group.", w, r, user) return @@ -1646,9 +1676,9 @@ func routePanelGroupsEditSubmit(w http.ResponseWriter, r *http.Request, user Use InternalError(err, w) return } - groups[gid].IsAdmin = false - groups[gid].IsMod = false - groups[gid].IsBanned = false + group.IsAdmin = false + group.IsMod = false + group.IsBanned = false default: LocalError("Invalid group type.", w, r, user) return @@ -1660,8 +1690,8 @@ func routePanelGroupsEditSubmit(w http.ResponseWriter, r *http.Request, user Use InternalError(err, w) return } - groups[gid].Name = gname - groups[gid].Tag = gtag + group.Name = gname + group.Tag = gtag 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 } - if !groupExists(gid) { + group, err := gstore.Get(gid) + if err == ErrNoRows { //log.Print("aaaaa monsters o.o") NotFound(w, r) return + } else if err != nil { + InternalError(err, w) + return } - group := groups[gid] if group.IsAdmin && !user.Perms.EditGroupAdmin { LocalError("You need the EditGroupAdmin permission to edit an admin group.", w, r, user) 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 { InternalError(err, w) return @@ -1942,32 +1975,32 @@ func routePanelLogsMod(w http.ResponseWriter, r *http.Request, user User) { return } - actor, err := users.CascadeGet(actorID) + actor, err := users.Get(actorID) if err != nil { actor = &User{Name: "Unknown", Link: buildProfileURL("unknown", 0)} } switch action { case "lock": - topic, err := topics.CascadeGet(elementID) + topic, err := topics.Get(elementID) if err != nil { topic = &Topic{Title: "Unknown", Link: buildProfileURL("unknown", 0)} } action = "" + topic.Title + " was locked by " + actor.Name + "" case "unlock": - topic, err := topics.CascadeGet(elementID) + topic, err := topics.Get(elementID) if err != nil { topic = &Topic{Title: "Unknown", Link: buildProfileURL("unknown", 0)} } action = "" + topic.Title + " was reopened by " + actor.Name + "" case "stick": - topic, err := topics.CascadeGet(elementID) + topic, err := topics.Get(elementID) if err != nil { topic = &Topic{Title: "Unknown", Link: buildProfileURL("unknown", 0)} } action = "" + topic.Title + " was pinned by " + actor.Name + "" case "unstick": - topic, err := topics.CascadeGet(elementID) + topic, err := topics.Get(elementID) if err != nil { 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 " + topic.Title + " was deleted by " + actor.Name + "" } case "ban": - targetUser, err := users.CascadeGet(elementID) + targetUser, err := users.Get(elementID) if err != nil { targetUser = &User{Name: "Unknown", Link: buildProfileURL("unknown", 0)} } action = "" + targetUser.Name + " was banned by " + actor.Name + "" case "unban": - targetUser, err := users.CascadeGet(elementID) + targetUser, err := users.Get(elementID) if err != nil { targetUser = &User{Name: "Unknown", Link: buildProfileURL("unknown", 0)} } action = "" + targetUser.Name + " was unbanned by " + actor.Name + "" case "activate": - targetUser, err := users.CascadeGet(elementID) + targetUser, err := users.Get(elementID) if err != nil { targetUser = &User{Name: "Unknown", Link: buildProfileURL("unknown", 0)} } diff --git a/permissions.go b/permissions.go index f127a213..094216b4 100644 --- a/permissions.go +++ b/permissions.go @@ -298,6 +298,7 @@ func permmapToQuery(permmap map[string]ForumPerms, fid int) error { return rebuildForumPermissions(fid) } +// TODO: Need a more thread-safe way of doing this. Possibly with sync.Map? func rebuildForumPermissions(fid int) error { if dev.DebugMode { log.Print("Loading the forum permissions") @@ -336,38 +337,44 @@ func rebuildForumPermissions(fid int) error { } forumPerms[gid][fid] = pperms } - for gid := range groups { + + groups, err := gstore.GetAll() + if err != nil { + return err + } + + for _, group := range groups { 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 blankIntList []int - groups[gid].Forums = blankList - groups[gid].CanSee = blankIntList + group.Forums = blankList + group.CanSee = blankIntList for ffid := range forums { - forumPerm, ok := forumPerms[gid][ffid] + forumPerm, ok := forumPerms[group.ID][ffid] if ok { //log.Print("Overriding permissions for forum #" + strconv.Itoa(fid)) - groups[gid].Forums = append(groups[gid].Forums, forumPerm) + group.Forums = append(group.Forums, forumPerm) } else { //log.Print("Inheriting from default for forum #" + strconv.Itoa(fid)) forumPerm = BlankForumPerms - groups[gid].Forums = append(groups[gid].Forums, forumPerm) + group.Forums = append(group.Forums, forumPerm) } if forumPerm.Overrides { if forumPerm.ViewTopic { - groups[gid].CanSee = append(groups[gid].CanSee, ffid) + group.CanSee = append(group.CanSee, ffid) } - } else if groups[gid].Perms.ViewTopic { - groups[gid].CanSee = append(groups[gid].CanSee, ffid) + } else if group.Perms.ViewTopic { + group.CanSee = append(group.CanSee, ffid) } } if dev.SuperDebug { - log.Printf("groups[gid].CanSee %+v\n", groups[gid].CanSee) - log.Printf("groups[gid].Forums %+v\n", groups[gid].Forums) - log.Print("len(groups[gid].Forums)", len(groups[gid].Forums)) + log.Printf("group.CanSee %+v\n", group.CanSee) + log.Printf("group.Forums %+v\n", group.Forums) + log.Print("len(group.Forums)", len(group.Forums)) } } return nil @@ -410,30 +417,36 @@ func buildForumPermissions() error { } forumPerms[gid][fid] = pperms } - for gid := range groups { + + groups, err := gstore.GetAll() + if err != nil { + return err + + } + for _, group := range groups { 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 for fid := range forums { - forumPerm, ok := forumPerms[gid][fid] + forumPerm, ok := forumPerms[group.ID][fid] if ok { // Override group perms //log.Print("Overriding permissions for forum #" + strconv.Itoa(fid)) - groups[gid].Forums = append(groups[gid].Forums, forumPerm) + group.Forums = append(group.Forums, forumPerm) } else { // Inherit from Group //log.Print("Inheriting from default for forum #" + strconv.Itoa(fid)) forumPerm = BlankForumPerms - groups[gid].Forums = append(groups[gid].Forums, forumPerm) + group.Forums = append(group.Forums, forumPerm) } if forumPerm.Overrides { if forumPerm.ViewTopic { - groups[gid].CanSee = append(groups[gid].CanSee, fid) + group.CanSee = append(group.CanSee, fid) } - } else if groups[gid].Perms.ViewTopic { - groups[gid].CanSee = append(groups[gid].CanSee, fid) + } else if group.Perms.ViewTopic { + group.CanSee = append(group.CanSee, fid) } } if dev.SuperDebug { @@ -547,7 +560,11 @@ func rebuildGroupPermissions(gid int) error { return err } - groups[gid].Perms = tmpPerms + group, err := gstore.Get(gid) + if err != nil { + return err + } + group.Perms = tmpPerms return nil } diff --git a/plugin_socialgroups.go b/plugin_socialgroups.go index ae54db21..f0b488f7 100644 --- a/plugin_socialgroups.go +++ b/plugin_socialgroups.go @@ -395,7 +395,7 @@ func socialgroupsCreateGroupSubmit(w http.ResponseWriter, r *http.Request, user } // Create the backing forum - fid, err := fstore.CreateForum(groupName, "", true, "") + fid, err := fstore.Create(groupName, "", true, "") if err != nil { InternalError(err, w) return diff --git a/reply.go b/reply.go index 201d13a2..ff1adfb6 100644 --- a/reply.go +++ b/reply.go @@ -6,7 +6,7 @@ */ package main -// Should we add a reply store to centralise all the reply logic? Would this cover profile replies too or would that be seperate? +// ? - Should we add a reply store to centralise all the reply logic? Would this cover profile replies too or would that be seperate? type Reply struct /* Should probably rename this to ReplyUser and rename ReplyShort to Reply */ { diff --git a/routes.go b/routes.go index 4f5057d6..e68194b9 100644 --- a/routes.go +++ b/routes.go @@ -157,7 +157,13 @@ func routeTopics(w http.ResponseWriter, r *http.Request, user User) { var qlist string 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 { if fstore.DirtyGet(fid).Name != "" { 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? - userList, err := users.BulkCascadeGetMap(idSlice) + userList, err := users.BulkGetMap(idSlice) if err != nil { InternalError(err, w) return @@ -279,7 +285,7 @@ func routeForum(w http.ResponseWriter, r *http.Request, user User, sfid string) } // TODO: Fix this double-check - forum, err := fstore.CascadeGet(fid) + forum, err := fstore.Get(fid) if err == ErrNoRows { NotFound(w, r) 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? - userList, err := users.BulkCascadeGetMap(idSlice) + userList, err := users.BulkGetMap(idSlice) if err != nil { InternalError(err, w) return @@ -387,7 +393,12 @@ func routeForums(w http.ResponseWriter, r *http.Request, user User) { } //log.Print("canSee",canSee) } 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 //log.Print("group.CanSee",group.CanSee) } @@ -471,8 +482,14 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) { user.Perms.CreateReply = false } - topic.Tag = groups[topic.Group].Tag - if groups[topic.Group].IsMod || groups[topic.Group].IsAdmin { + postGroup, err := gstore.Get(topic.Group) + if err != nil { + InternalError(err, w) + return + } + + topic.Tag = postGroup.Tag + if postGroup.IsMod || postGroup.IsAdmin { topic.ClassName = config.StaffCss } @@ -527,7 +544,13 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) { replyItem.ContentHtml = parseMessage(replyItem.Content) 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 } else { 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.Tag = groups[replyItem.Group].Tag + replyItem.Tag = postGroup.Tag /*if headerVars.Settings["url_tags"] == false { replyItem.URLName = "" @@ -631,7 +654,7 @@ func routeProfile(w http.ResponseWriter, r *http.Request, user User) { puser = &user } else { // Fetch the user data - puser, err = users.CascadeGet(pid) + puser, err = users.Get(pid) if err == ErrNoRows { NotFound(w, r) return @@ -656,8 +679,14 @@ func routeProfile(w http.ResponseWriter, r *http.Request, user User) { return } + group, err := gstore.Get(replyGroup) + if err != nil { + InternalError(err, w) + return + } + replyLines = strings.Count(replyContent, "\n") - if groups[replyGroup].IsMod || groups[replyGroup].IsAdmin { + if group.IsMod || group.IsAdmin { replyClassName = config.StaffCss } else { 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) } - if groups[replyGroup].Tag != "" { - replyTag = groups[replyGroup].Tag + if group.Tag != "" { + replyTag = group.Tag } else if puser.ID == replyCreatedBy { replyTag = "Profile Owner" } else { @@ -739,7 +768,7 @@ func routeLoginSubmit(w http.ResponseWriter, r *http.Request, user User) { return } - userPtr, err := users.CascadeGet(uid) + userPtr, err := users.Get(uid) if err != nil { LocalError("Bad account", w, r, user) return @@ -848,7 +877,7 @@ func routeRegisterSubmit(w http.ResponseWriter, r *http.Request, user User) { group = config.ActivationGroup } - uid, err := users.CreateUser(username, password, email, group, active) + uid, err := users.Create(username, password, email, group, active) if err == errAccountExists { LocalError("This username isn't available. Try another.", w, r, user) return diff --git a/routes_common.go b/routes_common.go index f8007d23..5fdf8c7d 100644 --- a/routes_common.go +++ b/routes_common.go @@ -3,19 +3,23 @@ package main import ( "html" "html/template" + "log" "net" "net/http" + "strconv" "strings" ) // nolint 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 var PanelUserCheck func(http.ResponseWriter, *http.Request, *User) (*HeaderVars, PanelStats, bool) = panelUserCheck 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 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 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 { user.Perms.ViewTopic = fperms.ViewTopic 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("fperms: %+v\n", fperms) if fperms.Overrides && !user.IsSuperAdmin { @@ -201,6 +219,16 @@ func simplePanelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (h 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 func simpleUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, success bool) { headerLite = &HeaderLite{ diff --git a/tasks.go b/tasks.go index 11a5e71f..5ad4a56b 100644 --- a/tasks.go +++ b/tasks.go @@ -35,7 +35,7 @@ func handleExpiredScheduledGroups() error { if err != nil { return err } - _ = users.Load(uid) + _ = users.Reload(uid) } return rows.Err() } diff --git a/template_list.go b/template_list.go index 4d3c982e..0ed3f9c2 100644 --- a/template_list.go +++ b/template_list.go @@ -453,47 +453,48 @@ var profile_0 = []byte(` -
+
`) +var profile_2 = []byte(` `) -var profile_2 = []byte(``) -var profile_3 = []byte(``) -var profile_4 = []byte(``) -var profile_5 = []byte(` +var profile_3 = []byte(``) +var profile_4 = []byte(``) +var profile_5 = []byte(``) +var profile_6 = []byte(`
`) -var profile_6 = []byte(`
+var profile_7 = []byte(`
`) -var profile_7 = []byte(`Unban +var profile_8 = []byte(`Unban `) -var profile_10 = []byte(`Ban`) -var profile_11 = []byte(` -
`) +var profile_11 = []byte(`Ban`) var profile_12 = []byte(` +
`) +var profile_13 = []byte(`
Report +var profile_14 = []byte(`?session=`) +var profile_15 = []byte(`&type=user" class="profile_menu_item report_item">Report
`) -var profile_15 = []byte(` +var profile_16 = []byte(`