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:
parent
b02897e6e1
commit
2557eb935b
@ -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.
|
||||
|
||||
|
10
alerts.go
10
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")
|
||||
}
|
||||
|
6
auth.go
6
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
|
||||
}
|
||||
|
18
cache.go
18
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
|
||||
}
|
||||
|
52
database.go
52
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()
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
104
group.go
104
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 != ""
|
||||
}
|
||||
|
204
group_store.go
204
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
|
||||
}
|
||||
|
2
main.go
2
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)
|
||||
|
||||
|
@ -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
|
||||
|
216
misc_test.go
216
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) {
|
||||
|
@ -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
|
||||
|
6
pages.go
6
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
|
||||
|
131
panel_routes.go
131
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 = "<a href='" + topic.Link + "'>" + topic.Title + "</a> was locked by <a href='" + actor.Link + "'>" + actor.Name + "</a>"
|
||||
case "unlock":
|
||||
topic, err := topics.CascadeGet(elementID)
|
||||
topic, err := topics.Get(elementID)
|
||||
if err != nil {
|
||||
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>"
|
||||
case "stick":
|
||||
topic, err := topics.CascadeGet(elementID)
|
||||
topic, err := topics.Get(elementID)
|
||||
if err != nil {
|
||||
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>"
|
||||
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 <a href='" + topic.Link + "'>" + topic.Title + "</a> was deleted by <a href='" + actor.Link + "'>" + actor.Name + "</a>"
|
||||
}
|
||||
case "ban":
|
||||
targetUser, err := users.CascadeGet(elementID)
|
||||
targetUser, err := users.Get(elementID)
|
||||
if err != nil {
|
||||
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>"
|
||||
case "unban":
|
||||
targetUser, err := users.CascadeGet(elementID)
|
||||
targetUser, err := users.Get(elementID)
|
||||
if err != nil {
|
||||
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>"
|
||||
case "activate":
|
||||
targetUser, err := users.CascadeGet(elementID)
|
||||
targetUser, err := users.Get(elementID)
|
||||
if err != nil {
|
||||
targetUser = &User{Name: "Unknown", Link: buildProfileURL("unknown", 0)}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
2
reply.go
2
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 */
|
||||
{
|
||||
|
59
routes.go
59
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
|
||||
|
@ -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{
|
||||
|
2
tasks.go
2
tasks.go
@ -35,7 +35,7 @@ func handleExpiredScheduledGroups() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = users.Load(uid)
|
||||
_ = users.Reload(uid)
|
||||
}
|
||||
return rows.Err()
|
||||
}
|
||||
|
@ -453,47 +453,48 @@ var profile_0 = []byte(`
|
||||
<img src="`)
|
||||
var profile_1 = []byte(`" class="avatar" />
|
||||
</div>
|
||||
<div class="rowitem">
|
||||
<div class="rowitem">`)
|
||||
var profile_2 = []byte(`
|
||||
<span class="profileName">`)
|
||||
var profile_2 = []byte(`</span>`)
|
||||
var profile_3 = []byte(`<span class="username" style="float: right;font-weight: normal;">`)
|
||||
var profile_4 = []byte(`</span>`)
|
||||
var profile_5 = []byte(`
|
||||
var profile_3 = []byte(`</span>`)
|
||||
var profile_4 = []byte(`<span class="username" style="float: right;font-weight: normal;">`)
|
||||
var profile_5 = []byte(`</span>`)
|
||||
var profile_6 = []byte(`
|
||||
</div>
|
||||
<div class="rowitem passive">
|
||||
<a class="profile_menu_item">Add Friend</a>
|
||||
</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(`?session=`)
|
||||
var profile_9 = []byte(`" class="profile_menu_item">Unban</a>
|
||||
var profile_8 = []byte(`<a href="/users/unban/`)
|
||||
var profile_9 = []byte(`?session=`)
|
||||
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(`
|
||||
</div>`)
|
||||
var profile_11 = []byte(`<a href="#ban_user" class="profile_menu_item">Ban</a>`)
|
||||
var profile_12 = []byte(`
|
||||
</div>`)
|
||||
var profile_13 = []byte(`
|
||||
<div class="rowitem passive">
|
||||
<a href="/report/submit/`)
|
||||
var profile_13 = []byte(`?session=`)
|
||||
var profile_14 = []byte(`&type=user" class="profile_menu_item report_item">Report</a>
|
||||
var profile_14 = []byte(`?session=`)
|
||||
var profile_15 = []byte(`&type=user" class="profile_menu_item report_item">Report</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="profile_right_lane" class="colstack_right">
|
||||
`)
|
||||
var profile_15 = []byte(`
|
||||
var profile_16 = []byte(`
|
||||
<!-- TODO: Inline the display: none; CSS -->
|
||||
<div id="ban_user_head" class="colstack_item colstack_head hash_hide ban_user_hash" style="display: none;">
|
||||
<div class="rowitem"><h1>Ban User</h1></div>
|
||||
</div>
|
||||
<form id="ban_user_form" class="hash_hide ban_user_hash" action="/users/ban/submit/`)
|
||||
var profile_16 = []byte(`?session=`)
|
||||
var profile_17 = []byte(`" method="post" style="display: none;">
|
||||
var profile_17 = []byte(`?session=`)
|
||||
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="colstack_item">
|
||||
<div class="formrow real_first_child">
|
||||
@ -524,54 +525,53 @@ var profile_18 = []byte(`
|
||||
</div>
|
||||
</form>
|
||||
`)
|
||||
var profile_19 = []byte(`
|
||||
var profile_20 = []byte(`
|
||||
|
||||
<div id="profile_comments_head" class="colstack_item colstack_head hash_hide">
|
||||
<div class="rowitem"><h1>Comments</h1></div>
|
||||
</div>
|
||||
<div id="profile_comments" class="colstack_item hash_hide" style="overflow: hidden;border-top: none;">`)
|
||||
var profile_20 = []byte(`
|
||||
<div id="profile_comments" class="colstack_item hash_hide">`)
|
||||
var profile_21 = []byte(`
|
||||
<div class="rowitem passive deletable_block editable_parent simple `)
|
||||
var profile_21 = []byte(`" style="`)
|
||||
var profile_22 = []byte(`background-image: url(`)
|
||||
var profile_23 = []byte(`), url(/static/post-avatar-bg.jpg);background-position: 0px `)
|
||||
var profile_24 = []byte(`-1`)
|
||||
var profile_25 = []byte(`0px;`)
|
||||
var profile_26 = []byte(`">
|
||||
var profile_22 = []byte(`" style="`)
|
||||
var profile_23 = []byte(`background-image: url(`)
|
||||
var profile_24 = []byte(`), url(/static/post-avatar-bg.jpg);background-position: 0px `)
|
||||
var profile_25 = []byte(`-1`)
|
||||
var profile_26 = []byte(`0px;`)
|
||||
var profile_27 = []byte(`">
|
||||
<span class="editable_block user_content simple">`)
|
||||
var profile_27 = []byte(`</span>
|
||||
|
||||
var profile_28 = []byte(`</span>
|
||||
<span class="controls">
|
||||
<a href="`)
|
||||
var profile_28 = []byte(`" class="real_username username">`)
|
||||
var profile_29 = []byte(`</a>
|
||||
var profile_29 = []byte(`" class="real_username username">`)
|
||||
var profile_30 = []byte(`</a>
|
||||
|
||||
`)
|
||||
var profile_30 = []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_31 = []byte(`<a href="/profile/reply/edit/submit/`)
|
||||
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/`)
|
||||
var profile_32 = []byte(`" class="mod_button" title="Delete Item"><button class="username delete_item trash_label"></button></a>`)
|
||||
var profile_33 = []byte(`
|
||||
var profile_33 = []byte(`" class="mod_button" title="Delete Item"><button class="username delete_item trash_label"></button></a>`)
|
||||
var profile_34 = []byte(`
|
||||
|
||||
<a class="mod_button" href="/report/submit/`)
|
||||
var profile_34 = []byte(`?session=`)
|
||||
var profile_35 = []byte(`&type=user-reply"><button class="username report_item flag_label"></button></a>
|
||||
var profile_35 = []byte(`?session=`)
|
||||
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>`)
|
||||
var profile_38 = []byte(`
|
||||
var profile_37 = []byte(`<a class="username hide_on_mobile user_tag" style="float: right;">`)
|
||||
var profile_38 = []byte(`</a>`)
|
||||
var profile_39 = []byte(`
|
||||
</span>
|
||||
</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">
|
||||
<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="formrow">
|
||||
<div class="formitem"><textarea name="reply-content" placeholder="Insert reply here"></textarea></div>
|
||||
@ -582,11 +582,11 @@ var profile_41 = []byte(`' type="hidden" />
|
||||
</div>
|
||||
</form>
|
||||
`)
|
||||
var profile_42 = []byte(`
|
||||
var profile_43 = []byte(`
|
||||
</div>
|
||||
|
||||
`)
|
||||
var profile_43 = []byte(`
|
||||
var profile_44 = []byte(`
|
||||
<script type="text/javascript">
|
||||
function handle_profile_hashbit() {
|
||||
var hash_class = ""
|
||||
|
@ -73,90 +73,91 @@ w.Write(header_16)
|
||||
w.Write(profile_0)
|
||||
w.Write([]byte(tmpl_profile_vars.ProfileOwner.Avatar))
|
||||
w.Write(profile_1)
|
||||
w.Write([]byte(tmpl_profile_vars.ProfileOwner.Name))
|
||||
w.Write(profile_2)
|
||||
if tmpl_profile_vars.ProfileOwner.Tag != "" {
|
||||
w.Write([]byte(tmpl_profile_vars.ProfileOwner.Name))
|
||||
w.Write(profile_3)
|
||||
w.Write([]byte(tmpl_profile_vars.ProfileOwner.Tag))
|
||||
if tmpl_profile_vars.ProfileOwner.Tag != "" {
|
||||
w.Write(profile_4)
|
||||
}
|
||||
w.Write([]byte(tmpl_profile_vars.ProfileOwner.Tag))
|
||||
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_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_20)
|
||||
if len(tmpl_profile_vars.ItemList) != 0 {
|
||||
for _, item := range tmpl_profile_vars.ItemList {
|
||||
w.Write(profile_20)
|
||||
w.Write([]byte(item.ClassName))
|
||||
w.Write(profile_21)
|
||||
if item.Avatar != "" {
|
||||
w.Write([]byte(item.ClassName))
|
||||
w.Write(profile_22)
|
||||
w.Write([]byte(item.Avatar))
|
||||
if item.Avatar != "" {
|
||||
w.Write(profile_23)
|
||||
if item.ContentLines <= 5 {
|
||||
w.Write([]byte(item.Avatar))
|
||||
w.Write(profile_24)
|
||||
}
|
||||
if item.ContentLines <= 5 {
|
||||
w.Write(profile_25)
|
||||
}
|
||||
w.Write(profile_26)
|
||||
w.Write([]byte(item.ContentHtml))
|
||||
}
|
||||
w.Write(profile_27)
|
||||
w.Write([]byte(item.UserLink))
|
||||
w.Write([]byte(item.ContentHtml))
|
||||
w.Write(profile_28)
|
||||
w.Write([]byte(item.CreatedByName))
|
||||
w.Write([]byte(item.UserLink))
|
||||
w.Write(profile_29)
|
||||
if tmpl_profile_vars.CurrentUser.IsMod {
|
||||
w.Write([]byte(item.CreatedByName))
|
||||
w.Write(profile_30)
|
||||
w.Write([]byte(strconv.Itoa(item.ID)))
|
||||
if tmpl_profile_vars.CurrentUser.IsMod {
|
||||
w.Write(profile_31)
|
||||
w.Write([]byte(strconv.Itoa(item.ID)))
|
||||
w.Write(profile_32)
|
||||
}
|
||||
w.Write(profile_33)
|
||||
w.Write([]byte(strconv.Itoa(item.ID)))
|
||||
w.Write(profile_34)
|
||||
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_33)
|
||||
}
|
||||
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_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_43)
|
||||
w.Write(profile_44)
|
||||
w.Write(footer_0)
|
||||
if len(tmpl_profile_vars.Header.Themes) != 0 {
|
||||
for _, item := range tmpl_profile_vars.Header.Themes {
|
||||
|
@ -8,7 +8,7 @@
|
||||
<div class="rowitem avatarRow" style="padding: 0;">
|
||||
<img src="{{.ProfileOwner.Avatar}}" class="avatar" />
|
||||
</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}}
|
||||
</div>
|
||||
<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 class="rowitem"><h1>Ban User</h1></div>
|
||||
</div>
|
||||
<form id="ban_user_form" class="hash_hide ban_user_hash" action="/users/ban/submit/{{.ProfileOwner.ID}}?session={{.CurrentUser.Session}}" method="post" style="display: none;">
|
||||
<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? **/}}
|
||||
<div class="colline">If all the fields are left blank, the ban will be permanent.</div>
|
||||
<div class="colstack_item">
|
||||
@ -66,10 +66,9 @@
|
||||
<div id="profile_comments_head" class="colstack_item colstack_head hash_hide">
|
||||
<div class="rowitem"><h1>Comments</h1></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}}">
|
||||
<span class="editable_block user_content simple">{{.ContentHtml}}</span>
|
||||
|
||||
<span class="controls">
|
||||
<a href="{{.UserLink}}" class="real_username username">{{.CreatedByName}}</a>
|
||||
|
||||
|
@ -190,9 +190,6 @@ li a {
|
||||
}
|
||||
}
|
||||
|
||||
.rowlist {
|
||||
font-size: 14px;
|
||||
}
|
||||
.rowlist .rowitem {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
@ -206,12 +203,12 @@ li a {
|
||||
.colstack_left {
|
||||
float: left;
|
||||
width: 30%;
|
||||
margin-right: 8px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.colstack_right {
|
||||
float: left;
|
||||
width: 65%;
|
||||
width: calc(70% - 15px);
|
||||
width: calc(70% - 13px);
|
||||
}
|
||||
.colstack_item {
|
||||
border: 1px solid hsl(0,0%,80%);
|
||||
@ -458,11 +455,7 @@ button.username {
|
||||
}
|
||||
|
||||
/* We'll be rewriting the profiles soon too! */
|
||||
/*.username.real_username {
|
||||
color: #404040;
|
||||
font-size: 16px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
/*.username.real_username { color: #404040; font-size: 16px; padding-right: 4px; }
|
||||
.username.real_username:hover { color: black; }*/
|
||||
|
||||
.postQuote {
|
||||
@ -564,11 +557,41 @@ button.username {
|
||||
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 {
|
||||
background-repeat: no-repeat, repeat-y;
|
||||
background-size: 128px;
|
||||
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 {
|
||||
background-color: white;
|
||||
@ -607,7 +630,7 @@ button.username {
|
||||
position: sticky;
|
||||
top: 4px;
|
||||
/*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 {
|
||||
background-repeat: no-repeat, repeat-y;
|
||||
@ -714,13 +737,13 @@ button.username {
|
||||
|
||||
/* Firefox specific CSS */
|
||||
@supports (-moz-appearance: none) {
|
||||
.footer {
|
||||
.footer, .rowmenu, #profile_right_lane .topic_reply_form {
|
||||
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 */
|
||||
@supports (-ms-ime-align:auto) {
|
||||
.footer {
|
||||
.footer, .rowmenu, #profile_right_lane .topic_reply_form {
|
||||
border-bottom: 1.5px inset hsl(0,0%,100%);
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
padding-bottom: 2px;
|
||||
|
||||
color: black;
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.panel_floater {
|
||||
|
@ -625,7 +625,9 @@ button.username {
|
||||
/* Profiles */
|
||||
#profile_left_lane {
|
||||
width: 220px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
#profile_left_pane {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
#profile_left_lane .avatarRow {
|
||||
overflow: hidden;
|
||||
@ -647,6 +649,10 @@ button.username {
|
||||
#profile_right_lane {
|
||||
width: calc(100% - 245px);
|
||||
}
|
||||
#profile_comments {
|
||||
overflow: hidden;
|
||||
border-top: none;
|
||||
}
|
||||
.simple .user_tag {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
25
topic.go
25
topic.go
@ -90,23 +90,26 @@ type TopicsRow struct {
|
||||
ForumLink string
|
||||
}
|
||||
|
||||
// TODO: Refactor the caller to take a Topic and a User rather than a combined TopicUser
|
||||
func getTopicuser(tid int) (TopicUser, error) {
|
||||
if config.CacheTopicUser != CACHE_SQL {
|
||||
topic, err := topics.Get(tid)
|
||||
tcache, tok := topics.(TopicCache)
|
||||
ucache, uok := users.(UserCache)
|
||||
if tok && uok {
|
||||
topic, err := tcache.CacheGet(tid)
|
||||
if err == nil {
|
||||
user, err := users.CascadeGet(topic.CreatedBy)
|
||||
user, err := users.Get(topic.CreatedBy)
|
||||
if err != nil {
|
||||
return TopicUser{ID: tid}, err
|
||||
}
|
||||
|
||||
// We might be better off just passing seperate topic and user structs to the caller?
|
||||
return copyTopicToTopicuser(topic, user), nil
|
||||
} else if users.GetLength() < users.GetCapacity() {
|
||||
topic, err = topics.CascadeGet(tid)
|
||||
} else if ucache.GetLength() < ucache.GetCapacity() {
|
||||
topic, err = topics.Get(tid)
|
||||
if err != nil {
|
||||
return TopicUser{ID: tid}, err
|
||||
}
|
||||
user, err := users.CascadeGet(topic.CreatedBy)
|
||||
user, err := users.Get(topic.CreatedBy)
|
||||
if err != nil {
|
||||
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)
|
||||
tu.Link = buildTopicURL(nameToSlug(tu.Title), tu.ID)
|
||||
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}
|
||||
//log.Printf("the_topic: %+v\n", the_topic)
|
||||
tu.Tag = groups[tu.Group].Tag
|
||||
_ = topics.Add(&theTopic)
|
||||
if tok {
|
||||
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}
|
||||
//log.Printf("the_topic: %+v\n", theTopic)
|
||||
_ = tcache.CacheAdd(&theTopic)
|
||||
}
|
||||
return tu, err
|
||||
}
|
||||
|
||||
|
239
topic_store.go
239
topic_store.go
@ -6,52 +6,76 @@
|
||||
*/
|
||||
package main
|
||||
|
||||
import "log"
|
||||
import "sync"
|
||||
import "database/sql"
|
||||
import "./query_gen/lib"
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"./query_gen/lib"
|
||||
)
|
||||
|
||||
// TODO: Add the watchdog goroutine
|
||||
// TODO: Add BulkGetMap
|
||||
// TODO: Add some sort of update method
|
||||
var topics TopicStore
|
||||
|
||||
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)
|
||||
GetUnsafe(id int) (*Topic, error)
|
||||
CascadeGet(id int) (*Topic, error)
|
||||
BypassGet(id int) (*Topic, error)
|
||||
Set(item *Topic) error
|
||||
Add(item *Topic) error
|
||||
AddUnsafe(item *Topic) error
|
||||
Remove(id int) error
|
||||
RemoveUnsafe(id int) error
|
||||
Delete(id int) error
|
||||
Exists(id int) bool
|
||||
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
|
||||
SetCapacity(capacity int)
|
||||
GetCapacity() int
|
||||
}
|
||||
|
||||
type MemoryTopicStore struct {
|
||||
items map[int]*Topic
|
||||
length int
|
||||
capacity int
|
||||
get *sql.Stmt
|
||||
items map[int]*Topic
|
||||
length int
|
||||
capacity int
|
||||
get *sql.Stmt
|
||||
exists *sql.Stmt
|
||||
topicCount *sql.Stmt
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewMemoryTopicStore gives you a new instance of 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 {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return &MemoryTopicStore{
|
||||
items: make(map[int]*Topic),
|
||||
capacity: capacity,
|
||||
get: stmt,
|
||||
items: make(map[int]*Topic),
|
||||
capacity: capacity,
|
||||
get: getStmt,
|
||||
exists: existsStmt,
|
||||
topicCount: topicCountStmt,
|
||||
}
|
||||
}
|
||||
|
||||
func (sts *MemoryTopicStore) Get(id int) (*Topic, error) {
|
||||
func (sts *MemoryTopicStore) CacheGet(id int) (*Topic, error) {
|
||||
sts.RLock()
|
||||
item, ok := sts.items[id]
|
||||
sts.RUnlock()
|
||||
@ -61,7 +85,7 @@ func (sts *MemoryTopicStore) Get(id int) (*Topic, error) {
|
||||
return item, ErrNoRows
|
||||
}
|
||||
|
||||
func (sts *MemoryTopicStore) GetUnsafe(id int) (*Topic, error) {
|
||||
func (sts *MemoryTopicStore) CacheGetUnsafe(id int) (*Topic, error) {
|
||||
item, ok := sts.items[id]
|
||||
if ok {
|
||||
return item, nil
|
||||
@ -69,7 +93,7 @@ func (sts *MemoryTopicStore) GetUnsafe(id int) (*Topic, error) {
|
||||
return item, ErrNoRows
|
||||
}
|
||||
|
||||
func (sts *MemoryTopicStore) CascadeGet(id int) (*Topic, error) {
|
||||
func (sts *MemoryTopicStore) Get(id int) (*Topic, error) {
|
||||
sts.RLock()
|
||||
topic, ok := sts.items[id]
|
||||
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)
|
||||
if err == nil {
|
||||
topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
|
||||
_ = sts.Add(topic)
|
||||
_ = sts.CacheAdd(topic)
|
||||
}
|
||||
return topic, err
|
||||
}
|
||||
@ -93,19 +117,58 @@ func (sts *MemoryTopicStore) BypassGet(id int) (*Topic, error) {
|
||||
return topic, err
|
||||
}
|
||||
|
||||
func (sts *MemoryTopicStore) Load(id int) error {
|
||||
func (sts *MemoryTopicStore) Reload(id int) error {
|
||||
topic := &Topic{ID: id}
|
||||
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
|
||||
if err == nil {
|
||||
topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
|
||||
_ = sts.Set(topic)
|
||||
_ = sts.CacheSet(topic)
|
||||
} else {
|
||||
_ = sts.Remove(id)
|
||||
_ = sts.CacheRemove(id)
|
||||
}
|
||||
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()
|
||||
_, ok := sts.items[item.ID]
|
||||
if ok {
|
||||
@ -121,7 +184,7 @@ func (sts *MemoryTopicStore) Set(item *Topic) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sts *MemoryTopicStore) Add(item *Topic) error {
|
||||
func (sts *MemoryTopicStore) CacheAdd(item *Topic) error {
|
||||
if sts.length >= sts.capacity {
|
||||
return ErrStoreCapacityOverflow
|
||||
}
|
||||
@ -132,7 +195,8 @@ func (sts *MemoryTopicStore) Add(item *Topic) error {
|
||||
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 {
|
||||
return ErrStoreCapacityOverflow
|
||||
}
|
||||
@ -141,7 +205,8 @@ func (sts *MemoryTopicStore) AddUnsafe(item *Topic) error {
|
||||
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()
|
||||
delete(sts.items, id)
|
||||
sts.Unlock()
|
||||
@ -149,12 +214,13 @@ func (sts *MemoryTopicStore) Remove(id int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sts *MemoryTopicStore) RemoveUnsafe(id int) error {
|
||||
func (sts *MemoryTopicStore) CacheRemoveUnsafe(id int) error {
|
||||
delete(sts.items, id)
|
||||
sts.length--
|
||||
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 {
|
||||
// Coming Soon...
|
||||
return nil
|
||||
@ -172,16 +238,40 @@ func (sts *MemoryTopicStore) GetCapacity() int {
|
||||
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 {
|
||||
get *sql.Stmt
|
||||
get *sql.Stmt
|
||||
exists *sql.Stmt
|
||||
topicCount *sql.Stmt
|
||||
}
|
||||
|
||||
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 {
|
||||
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) {
|
||||
@ -191,20 +281,6 @@ func (sts *SQLTopicStore) Get(id int) (*Topic, error) {
|
||||
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) {
|
||||
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)
|
||||
@ -212,37 +288,52 @@ func (sts *SQLTopicStore) BypassGet(id int) (*Topic, error) {
|
||||
return topic, err
|
||||
}
|
||||
|
||||
func (sts *SQLTopicStore) Load(id int) error {
|
||||
topic := Topic{ID: id}
|
||||
err := sts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Data)
|
||||
topic.Link = buildTopicURL(nameToSlug(topic.Title), id)
|
||||
func (sts *SQLTopicStore) Reload(id int) error {
|
||||
return sts.exists.QueryRow(id).Scan(&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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Coming Soon...
|
||||
return nil
|
||||
}
|
||||
func (sts *SQLTopicStore) GetCapacity() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (sts *SQLTopicStore) GetLength() int {
|
||||
return 0 // Return the total number of topics on the forums?
|
||||
// Return the total number of topics on these 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
29
user.go
@ -67,7 +67,7 @@ func (user *User) Unban() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return users.Load(user.ID)
|
||||
return users.Reload(user.ID)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
return users.Load(user.ID)
|
||||
return users.Reload(user.ID)
|
||||
}
|
||||
|
||||
// TODO: Use a transaction to avoid race conditions
|
||||
@ -100,7 +100,7 @@ func (user *User) RevertGroupUpdate() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return users.Load(user.ID)
|
||||
return users.Reload(user.ID)
|
||||
}
|
||||
|
||||
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) {
|
||||
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 {
|
||||
user.Group = user.TempGroup
|
||||
}
|
||||
|
||||
user.IsAdmin = user.IsSuperAdmin || groups[user.Group].IsAdmin
|
||||
user.IsSuperMod = user.IsAdmin || groups[user.Group].IsMod
|
||||
group := gstore.DirtyGet(user.Group)
|
||||
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.IsBanned = groups[user.Group].IsBanned
|
||||
user.IsBanned = group.IsBanned
|
||||
if user.IsBanned && user.IsSuperMod {
|
||||
user.IsBanned = false
|
||||
}
|
||||
|
170
user_store.go
170
user_store.go
@ -13,33 +13,40 @@ import (
|
||||
)
|
||||
|
||||
// TODO: Add the watchdog goroutine
|
||||
// TODO: Add some sort of update method
|
||||
var users UserStore
|
||||
var errAccountExists = errors.New("this username is already in use")
|
||||
|
||||
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)
|
||||
GetUnsafe(id int) (*User, error)
|
||||
CascadeGet(id int) (*User, error)
|
||||
//BulkCascadeGet(ids []int) ([]*User, error)
|
||||
BulkCascadeGetMap(ids []int) (map[int]*User, error)
|
||||
Exists(id int) bool
|
||||
//BulkGet(ids []int) ([]*User, error)
|
||||
BulkGetMap(ids []int) (map[int]*User, error)
|
||||
BypassGet(id int) (*User, error)
|
||||
Set(item *User) 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
|
||||
Create(username string, password string, email string, group int, active int) (int, error)
|
||||
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 {
|
||||
items map[int]*User
|
||||
length int
|
||||
capacity int
|
||||
get *sql.Stmt
|
||||
exists *sql.Stmt
|
||||
register *sql.Stmt
|
||||
usernameExists *sql.Stmt
|
||||
userCount *sql.Stmt
|
||||
@ -53,6 +60,11 @@ func NewMemoryUserStore(capacity int) *MemoryUserStore {
|
||||
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?
|
||||
// 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,'',?,''")
|
||||
@ -74,13 +86,14 @@ func NewMemoryUserStore(capacity int) *MemoryUserStore {
|
||||
items: make(map[int]*User),
|
||||
capacity: capacity,
|
||||
get: getStmt,
|
||||
exists: existsStmt,
|
||||
register: registerStmt,
|
||||
usernameExists: usernameExistsStmt,
|
||||
userCount: userCountStmt,
|
||||
}
|
||||
}
|
||||
|
||||
func (sus *MemoryUserStore) Get(id int) (*User, error) {
|
||||
func (sus *MemoryUserStore) CacheGet(id int) (*User, error) {
|
||||
sus.RLock()
|
||||
item, ok := sus.items[id]
|
||||
sus.RUnlock()
|
||||
@ -90,7 +103,7 @@ func (sus *MemoryUserStore) Get(id int) (*User, error) {
|
||||
return item, ErrNoRows
|
||||
}
|
||||
|
||||
func (sus *MemoryUserStore) GetUnsafe(id int) (*User, error) {
|
||||
func (sus *MemoryUserStore) CacheGetUnsafe(id int) (*User, error) {
|
||||
item, ok := sus.items[id]
|
||||
if ok {
|
||||
return item, nil
|
||||
@ -98,7 +111,7 @@ func (sus *MemoryUserStore) GetUnsafe(id int) (*User, error) {
|
||||
return item, ErrNoRows
|
||||
}
|
||||
|
||||
func (sus *MemoryUserStore) CascadeGet(id int) (*User, error) {
|
||||
func (sus *MemoryUserStore) Get(id int) (*User, error) {
|
||||
sus.RLock()
|
||||
user, ok := sus.items[id]
|
||||
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.Link = buildProfileURL(nameToSlug(user.Name), id)
|
||||
user.Tag = groups[user.Group].Tag
|
||||
user.Tag = gstore.DirtyGet(user.Group).Tag
|
||||
initUserPerms(user)
|
||||
if err == nil {
|
||||
sus.Set(user)
|
||||
sus.CacheSet(user)
|
||||
}
|
||||
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: 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)
|
||||
list = make(map[int]*User)
|
||||
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.Link = buildProfileURL(nameToSlug(user.Name), user.ID)
|
||||
user.Tag = groups[user.Group].Tag
|
||||
user.Tag = gstore.DirtyGet(user.Group).Tag
|
||||
initUserPerms(user)
|
||||
|
||||
// Add it to the cache...
|
||||
_ = sus.Set(user)
|
||||
_ = sus.CacheSet(user)
|
||||
|
||||
// Add it to the list to be returned
|
||||
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.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 *MemoryUserStore) Load(id int) error {
|
||||
func (sus *MemoryUserStore) Reload(id int) 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 err != nil {
|
||||
sus.Remove(id)
|
||||
sus.CacheRemove(id)
|
||||
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.Link = buildProfileURL(nameToSlug(user.Name), id)
|
||||
user.Tag = groups[user.Group].Tag
|
||||
user.Tag = gstore.DirtyGet(user.Group).Tag
|
||||
initUserPerms(user)
|
||||
_ = sus.Set(user)
|
||||
_ = sus.CacheSet(user)
|
||||
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()
|
||||
user, ok := sus.items[item.ID]
|
||||
if ok {
|
||||
@ -289,7 +306,7 @@ func (sus *MemoryUserStore) Set(item *User) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sus *MemoryUserStore) Add(item *User) error {
|
||||
func (sus *MemoryUserStore) CacheAdd(item *User) error {
|
||||
if sus.length >= sus.capacity {
|
||||
return ErrStoreCapacityOverflow
|
||||
}
|
||||
@ -300,7 +317,7 @@ func (sus *MemoryUserStore) Add(item *User) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sus *MemoryUserStore) AddUnsafe(item *User) error {
|
||||
func (sus *MemoryUserStore) CacheAddUnsafe(item *User) error {
|
||||
if sus.length >= sus.capacity {
|
||||
return ErrStoreCapacityOverflow
|
||||
}
|
||||
@ -309,7 +326,7 @@ func (sus *MemoryUserStore) AddUnsafe(item *User) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sus *MemoryUserStore) Remove(id int) error {
|
||||
func (sus *MemoryUserStore) CacheRemove(id int) error {
|
||||
sus.Lock()
|
||||
delete(sus.items, id)
|
||||
sus.Unlock()
|
||||
@ -317,13 +334,13 @@ func (sus *MemoryUserStore) Remove(id int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sus *MemoryUserStore) RemoveUnsafe(id int) error {
|
||||
func (sus *MemoryUserStore) CacheRemoveUnsafe(id int) error {
|
||||
delete(sus.items, id)
|
||||
sus.length--
|
||||
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..?
|
||||
err := sus.usernameExists.QueryRow(username).Scan(&username)
|
||||
if err != ErrNoRows {
|
||||
@ -373,6 +390,7 @@ func (sus *MemoryUserStore) GetGlobalCount() int {
|
||||
|
||||
type SQLUserStore struct {
|
||||
get *sql.Stmt
|
||||
exists *sql.Stmt
|
||||
register *sql.Stmt
|
||||
usernameExists *sql.Stmt
|
||||
userCount *sql.Stmt
|
||||
@ -384,6 +402,11 @@ func NewSQLUserStore() *SQLUserStore {
|
||||
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?
|
||||
// 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,'',?,''")
|
||||
@ -403,6 +426,7 @@ func NewSQLUserStore() *SQLUserStore {
|
||||
|
||||
return &SQLUserStore{
|
||||
get: getStmt,
|
||||
exists: existsStmt,
|
||||
register: registerStmt,
|
||||
usernameExists: usernameExistsStmt,
|
||||
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.Link = buildProfileURL(nameToSlug(user.Name), id)
|
||||
user.Tag = groups[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
|
||||
user.Tag = gstore.DirtyGet(user.Group).Tag
|
||||
initUserPerms(&user)
|
||||
return &user, err
|
||||
}
|
||||
|
||||
// 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 uidList []interface{}
|
||||
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.Link = buildProfileURL(nameToSlug(user.Name), user.ID)
|
||||
user.Tag = groups[user.Group].Tag
|
||||
user.Tag = gstore.DirtyGet(user.Group).Tag
|
||||
initUserPerms(user)
|
||||
|
||||
// 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.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) Load(id int) error {
|
||||
user := &User{ID: id}
|
||||
// Simplify this into a quick check to see whether the user exists. Add an Exists method to facilitate this?
|
||||
return sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
|
||||
func (sus *SQLUserStore) Reload(id int) error {
|
||||
return sus.exists.QueryRow(id).Scan(&id)
|
||||
}
|
||||
|
||||
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..?
|
||||
err := sus.usernameExists.QueryRow(username).Scan(&username)
|
||||
if err != ErrNoRows {
|
||||
@ -556,35 +548,7 @@ func (sus *SQLUserStore) CreateUser(username string, password string, email stri
|
||||
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
|
||||
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 {
|
||||
var ucount int
|
||||
err := sus.userCount.QueryRow().Scan(&ucount)
|
||||
|
7
utils.go
7
utils.go
@ -386,13 +386,6 @@ func getLevels(maxLevel int) []float64 {
|
||||
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 {
|
||||
if slug == "" {
|
||||
return strconv.Itoa(id)
|
||||
|
@ -168,7 +168,7 @@ func routeWebsockets(w http.ResponseWriter, r *http.Request, user User) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
userptr, err := users.CascadeGet(user.ID)
|
||||
userptr, err := users.Get(user.ID)
|
||||
if err != nil && err != ErrStoreCapacityOverflow {
|
||||
return
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user