Added the GroupStore.

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

View File

@ -31,7 +31,7 @@ Other modern features like alerts, likes, advanced dashboard with live stats (CP
# Dependencies
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.

View File

@ -29,13 +29,13 @@ import "errors"
func buildAlert(asid int, event string, elementType string, actorID int, targetUserID int, elementID int, user User /* The current user */) (string, error) {
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")
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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()

View File

@ -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 {

View File

@ -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
View File

@ -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 != ""
}

View File

@ -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
}

View File

@ -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)

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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)}
}

View File

@ -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
}

View File

@ -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

View File

@ -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 */
{

View File

@ -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

View File

@ -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{

View File

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

View File

@ -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>&nbsp;&nbsp;
var profile_29 = []byte(`" class="real_username username">`)
var profile_30 = []byte(`</a>&nbsp;&nbsp;
`)
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 = ""

View File

@ -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 {

View File

@ -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>&nbsp;&nbsp;

View File

@ -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%);
}
}

View File

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

View File

@ -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;
}

View File

@ -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
}

View File

@ -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
View File

@ -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
}

View File

@ -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)

View File

@ -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)

View File

@ -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
}