Refactored the GroupStore to make it more thread-safe.

Refactored the store initialisers to better propogate errors upwards.
Moved the user initialisation logic to a method on *User.
Added the Reload and GlobalCount methods to the GroupStore.
Added the CacheSet method to the GroupCache.

Renamed plugin_socialgroups to plugin_guilds 3/3
This commit is contained in:
Azareal 2017-11-02 13:35:19 +00:00
parent 3c13f4da7f
commit d0363f3eb1
18 changed files with 347 additions and 295 deletions

View File

@ -21,7 +21,10 @@ func initDatabase() (err error) {
} }
log.Print("Loading the usergroups.") log.Print("Loading the usergroups.")
gstore = NewMemoryGroupStore() gstore, err = NewMemoryGroupStore()
if err != nil {
return err
}
err = gstore.LoadGroups() err = gstore.LoadGroups()
if err != nil { if err != nil {
return err return err
@ -30,15 +33,30 @@ func initDatabase() (err error) {
// We have to put this here, otherwise LoadForums() won't be able to get the last poster data when building it's forums // We have to put this here, otherwise LoadForums() won't be able to get the last poster data when building it's forums
log.Print("Initialising the user and topic stores") log.Print("Initialising the user and topic stores")
if config.CacheTopicUser == CACHE_STATIC { if config.CacheTopicUser == CACHE_STATIC {
users = NewMemoryUserStore(config.UserCacheCapacity) users, err = NewMemoryUserStore(config.UserCacheCapacity)
topics = NewMemoryTopicStore(config.TopicCacheCapacity) if err != nil {
return err
}
topics, err = NewMemoryTopicStore(config.TopicCacheCapacity)
if err != nil {
return err
}
} else { } else {
users = NewSQLUserStore() users, err = NewSQLUserStore()
topics = NewSQLTopicStore() if err != nil {
return err
}
topics, err = NewSQLTopicStore()
if err != nil {
return err
}
} }
log.Print("Loading the forums.") log.Print("Loading the forums.")
fstore = NewMemoryForumStore() fstore, err = NewMemoryForumStore()
if err != nil {
return err
}
err = fstore.LoadForums() err = fstore.LoadForums()
if err != nil { if err != nil {
return err return err

View File

@ -37,9 +37,8 @@ func (err *RouteErrorImpl) Type() string {
// System errors may contain sensitive information we don't want the user to see // System errors may contain sensitive information we don't want the user to see
if err.system { if err.system {
return "system" return "system"
} else {
return "user"
} }
return "user"
} }
func (err *RouteErrorImpl) Error() string { func (err *RouteErrorImpl) Error() string {
@ -101,9 +100,8 @@ func InternalError(err error, w http.ResponseWriter, r *http.Request) RouteError
func InternalErrorJSQ(err error, w http.ResponseWriter, r *http.Request, isJs bool) RouteError { func InternalErrorJSQ(err error, w http.ResponseWriter, r *http.Request, isJs bool) RouteError {
if !isJs { if !isJs {
return InternalError(err, w, r) return InternalError(err, w, r)
} else {
return InternalErrorJS(err, w, r)
} }
return InternalErrorJS(err, w, r)
} }
// InternalErrorJS is the JSON version of InternalError on routes we know will only be requested via JSON. E.g. An API. // InternalErrorJS is the JSON version of InternalError on routes we know will only be requested via JSON. E.g. An API.
@ -160,9 +158,8 @@ func PreErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) RouteErro
func PreErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, isJs bool) RouteError { func PreErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, isJs bool) RouteError {
if !isJs { if !isJs {
return PreError(errmsg, w, r) return PreError(errmsg, w, r)
} else {
return PreErrorJS(errmsg, w, r)
} }
return PreErrorJS(errmsg, w, r)
} }
// LocalError is an error shown to the end-user when something goes wrong and it's not the software's fault // LocalError is an error shown to the end-user when something goes wrong and it's not the software's fault
@ -184,9 +181,8 @@ func LocalError(errmsg string, w http.ResponseWriter, r *http.Request, user User
func LocalErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, user User, isJs bool) RouteError { func LocalErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, user User, isJs bool) RouteError {
if !isJs { if !isJs {
return LocalError(errmsg, w, r, user) return LocalError(errmsg, w, r, user)
} else {
return LocalErrorJS(errmsg, w, r)
} }
return LocalErrorJS(errmsg, w, r)
} }
func LocalErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) RouteError { func LocalErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) RouteError {
@ -216,9 +212,8 @@ func NoPermissions(w http.ResponseWriter, r *http.Request, user User) RouteError
func NoPermissionsJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bool) RouteError { func NoPermissionsJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bool) RouteError {
if !isJs { if !isJs {
return NoPermissions(w, r, user) return NoPermissions(w, r, user)
} else {
return NoPermissionsJS(w, r, user)
} }
return NoPermissionsJS(w, r, user)
} }
func NoPermissionsJS(w http.ResponseWriter, r *http.Request, user User) RouteError { func NoPermissionsJS(w http.ResponseWriter, r *http.Request, user User) RouteError {
@ -248,9 +243,8 @@ func Banned(w http.ResponseWriter, r *http.Request, user User) RouteError {
func BannedJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bool) RouteError { func BannedJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bool) RouteError {
if !isJs { if !isJs {
return Banned(w, r, user) return Banned(w, r, user)
} else {
return BannedJS(w, r, user)
} }
return BannedJS(w, r, user)
} }
func BannedJS(w http.ResponseWriter, r *http.Request, user User) RouteError { func BannedJS(w http.ResponseWriter, r *http.Request, user User) RouteError {
@ -331,9 +325,8 @@ func CustomError(errmsg string, errcode int, errtitle string, w http.ResponseWri
func CustomErrorJSQ(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User, isJs bool) RouteError { func CustomErrorJSQ(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User, isJs bool) RouteError {
if !isJs { if !isJs {
return CustomError(errmsg, errcode, errtitle, w, r, user) return CustomError(errmsg, errcode, errtitle, w, r, user)
} else {
return CustomErrorJS(errmsg, errcode, errtitle, w, r, user)
} }
return CustomErrorJS(errmsg, errcode, errtitle, w, r, user)
} }
// CustomErrorJS is the pure JSON version of CustomError // CustomErrorJS is the pure JSON version of CustomError

View File

@ -0,0 +1,7 @@
{
"UName":"guilds",
"Name":"Guilds",
"Author":"Azareal",
"URL":"https://github.com/Azareal/Gosora",
"Skip":true
}

View File

@ -66,30 +66,30 @@ type MemoryForumStore struct {
} }
// NewMemoryForumStore gives you a new instance of MemoryForumStore // NewMemoryForumStore gives you a new instance of MemoryForumStore
func NewMemoryForumStore() *MemoryForumStore { func NewMemoryForumStore() (*MemoryForumStore, error) {
getStmt, err := qgen.Builder.SimpleSelect("forums", "name, desc, active, preset, parentID, parentType, topicCount, lastTopicID, lastReplyerID", "fid = ?", "", "") getStmt, err := qgen.Builder.SimpleSelect("forums", "name, desc, active, preset, parentID, parentType, topicCount, lastTopicID, lastReplyerID", "fid = ?", "", "")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
getAllStmt, err := qgen.Builder.SimpleSelect("forums", "fid, name, desc, active, preset, parentID, parentType, topicCount, lastTopicID, lastReplyerID", "", "fid ASC", "") getAllStmt, err := qgen.Builder.SimpleSelect("forums", "fid, name, desc, active, preset, parentID, parentType, topicCount, lastTopicID, lastReplyerID", "", "fid ASC", "")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
// TODO: Do a proper delete // TODO: Do a proper delete
deleteStmt, err := qgen.Builder.SimpleUpdate("forums", "name= '', active = 0", "fid = ?") deleteStmt, err := qgen.Builder.SimpleUpdate("forums", "name= '', active = 0", "fid = ?")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
forumCountStmt, err := qgen.Builder.SimpleCount("forums", "name != ''", "") forumCountStmt, err := qgen.Builder.SimpleCount("forums", "name != ''", "")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
return &MemoryForumStore{ return &MemoryForumStore{
get: getStmt, get: getStmt,
getAll: getAllStmt, getAll: getAllStmt,
delete: deleteStmt, delete: deleteStmt,
getForumCount: forumCountStmt, getForumCount: forumCountStmt,
} }, nil
} }
// TODO: Add support for subforums // TODO: Add support for subforums

View File

@ -11,7 +11,7 @@ type GroupAdmin struct {
CanDelete bool CanDelete bool
} }
// ! Fix the data races // ! Fix the data races in the fperms
type Group struct { type Group struct {
ID int ID int
Name string Name string
@ -27,26 +27,31 @@ type Group struct {
CanSee []int // The IDs of the forums this group can see CanSee []int // The IDs of the forums this group can see
} }
// TODO: Reload the group from the database rather than modifying it via it's pointer
func (group *Group) ChangeRank(isAdmin bool, isMod bool, isBanned bool) (err error) { func (group *Group) ChangeRank(isAdmin bool, isMod bool, isBanned bool) (err error) {
_, err = updateGroupRankStmt.Exec(isAdmin, isMod, isBanned, group.ID) _, err = updateGroupRankStmt.Exec(isAdmin, isMod, isBanned, group.ID)
if err != nil { if err != nil {
return err return err
} }
group.IsAdmin = isAdmin gstore.Reload(group.ID)
group.IsMod = isMod
if isAdmin || isMod {
group.IsBanned = false
} else {
group.IsBanned = isBanned
}
return nil return nil
} }
// ! Ahem, don't listen to the comment below. It's not concurrency safe right now.
// Copy gives you a non-pointer concurrency safe copy of the group // Copy gives you a non-pointer concurrency safe copy of the group
func (group *Group) Copy() Group { func (group *Group) Copy() Group {
return *group return *group
} }
// TODO: Replace this sorting mechanism with something a lot more efficient
// ? - Use sort.Slice instead?
type SortGroup []*Group
func (sg SortGroup) Len() int {
return len(sg)
}
func (sg SortGroup) Swap(i, j int) {
sg[i], sg[j] = sg[j], sg[i]
}
func (sg SortGroup) Less(i, j int) bool {
return sg[i].ID < sg[j].ID
}

View File

@ -2,16 +2,16 @@
package main package main
import ( import (
"database/sql"
"encoding/json" "encoding/json"
"errors" "errors"
"log" "log"
"sort"
"sync" "sync"
"./query_gen/lib" "./query_gen/lib"
) )
var groupCreateMutex sync.Mutex
var groupUpdateMutex sync.Mutex
var gstore GroupStore var gstore GroupStore
// ? - We could fallback onto the database when an item can't be found in the cache? // ? - We could fallback onto the database when an item can't be found in the cache?
@ -24,23 +24,40 @@ type GroupStore interface {
Create(name string, tag string, isAdmin bool, isMod bool, isBanned bool) (int, error) Create(name string, tag string, isAdmin bool, isMod bool, isBanned bool) (int, error)
GetAll() ([]*Group, error) GetAll() ([]*Group, error)
GetRange(lower int, higher int) ([]*Group, error) GetRange(lower int, higher int) ([]*Group, error)
Reload(id int) error // ? - Should we move this to GroupCache? It might require us to do some unnecessary casting though
GlobalCount() int
} }
type GroupCache interface { type GroupCache interface {
CacheSet(group *Group) error
Length() int Length() int
} }
type MemoryGroupStore struct { type MemoryGroupStore struct {
groups []*Group // TODO: Use a sync.Map instead of a slice groups map[int]*Group // TODO: Use a sync.Map instead of a map?
groupCapCount int groupCount int
get *sql.Stmt
sync.RWMutex
} }
func NewMemoryGroupStore() *MemoryGroupStore { func NewMemoryGroupStore() (*MemoryGroupStore, error) {
return &MemoryGroupStore{} getGroupStmt, err := qgen.Builder.SimpleSelect("users_groups", "name, permissions, plugin_perms, is_mod, is_admin, is_banned, tag", "gid = ?", "", "")
if err != nil {
return nil, err
}
return &MemoryGroupStore{
groups: make(map[int]*Group),
groupCount: 0,
get: getGroupStmt,
}, nil
} }
func (mgs *MemoryGroupStore) LoadGroups() error { func (mgs *MemoryGroupStore) LoadGroups() error {
mgs.groups = []*Group{&Group{ID: 0, Name: "Unknown"}} mgs.Lock()
defer mgs.Unlock()
mgs.groups[0] = &Group{ID: 0, Name: "Unknown"}
rows, err := getGroupsStmt.Query() rows, err := getGroupsStmt.Query()
if err != nil { if err != nil {
@ -50,79 +67,136 @@ func (mgs *MemoryGroupStore) LoadGroups() error {
i := 1 i := 1
for ; rows.Next(); i++ { for ; rows.Next(); i++ {
group := Group{ID: 0} group := &Group{ID: 0}
err := rows.Scan(&group.ID, &group.Name, &group.PermissionsText, &group.PluginPermsText, &group.IsMod, &group.IsAdmin, &group.IsBanned, &group.Tag) err := rows.Scan(&group.ID, &group.Name, &group.PermissionsText, &group.PluginPermsText, &group.IsMod, &group.IsAdmin, &group.IsBanned, &group.Tag)
if err != nil { if err != nil {
return err return err
} }
err = json.Unmarshal(group.PermissionsText, &group.Perms) err = mgs.initGroup(group)
if err != nil { if err != nil {
return err return err
} }
if dev.DebugMode { mgs.groups[group.ID] = group
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)
// TODO: Can we optimise the bit where this cascades down to the user now?
if group.IsAdmin || group.IsMod {
group.IsBanned = false
}
mgs.groups = append(mgs.groups, &group)
} }
err = rows.Err() err = rows.Err()
if err != nil { if err != nil {
return err return err
} }
mgs.groupCapCount = i mgs.groupCount = i
if dev.DebugMode { if dev.DebugMode {
log.Print("Binding the Not Loggedin Group") log.Print("Binding the Not Loggedin Group")
} }
GuestPerms = mgs.groups[6].Perms GuestPerms = mgs.dirtyGetUnsafe(6).Perms
return nil return nil
} }
func (mgs *MemoryGroupStore) DirtyGet(gid int) *Group { // TODO: Hit the database when the item isn't in memory
if !mgs.Exists(gid) { func (mgs *MemoryGroupStore) dirtyGetUnsafe(gid int) *Group {
group, ok := mgs.groups[gid]
if !ok {
return &blankGroup return &blankGroup
} }
return mgs.groups[gid] return group
} }
// TODO: Hit the database when the item isn't in memory
func (mgs *MemoryGroupStore) DirtyGet(gid int) *Group {
mgs.RLock()
group, ok := mgs.groups[gid]
mgs.RUnlock()
if !ok {
return &blankGroup
}
return group
}
// TODO: Hit the database when the item isn't in memory
func (mgs *MemoryGroupStore) Get(gid int) (*Group, error) { func (mgs *MemoryGroupStore) Get(gid int) (*Group, error) {
if !mgs.Exists(gid) { mgs.RLock()
group, ok := mgs.groups[gid]
mgs.RUnlock()
if !ok {
return nil, ErrNoRows return nil, ErrNoRows
} }
return mgs.groups[gid], nil return group, nil
} }
// TODO: Hit the database when the item isn't in memory
func (mgs *MemoryGroupStore) GetCopy(gid int) (Group, error) { func (mgs *MemoryGroupStore) GetCopy(gid int) (Group, error) {
if !mgs.Exists(gid) { mgs.RLock()
group, ok := mgs.groups[gid]
mgs.RUnlock()
if !ok {
return blankGroup, ErrNoRows return blankGroup, ErrNoRows
} }
return *mgs.groups[gid], nil return *group, nil
} }
func (mgs *MemoryGroupStore) Reload(id int) error {
var group = &Group{ID: id}
err := mgs.get.QueryRow(id).Scan(&group.Name, &group.PermissionsText, &group.PluginPermsText, &group.IsMod, &group.IsAdmin, &group.IsBanned, &group.Tag)
if err != nil {
return err
}
err = mgs.initGroup(group)
if err != nil {
LogError(err)
}
mgs.CacheSet(group)
err = rebuildGroupPermissions(id)
if err != nil {
LogError(err)
}
return nil
}
func (mgs *MemoryGroupStore) initGroup(group *Group) error {
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)
// TODO: Can we optimise the bit where this cascades down to the user now?
if group.IsAdmin || group.IsMod {
group.IsBanned = false
}
return nil
}
func (mgs *MemoryGroupStore) CacheSet(group *Group) error {
mgs.Lock()
mgs.groups[group.ID] = group
mgs.Unlock()
return nil
}
// TODO: Hit the database when the item isn't in memory
func (mgs *MemoryGroupStore) Exists(gid int) bool { func (mgs *MemoryGroupStore) Exists(gid int) bool {
return (gid <= mgs.groupCapCount) && (gid >= 0) && mgs.groups[gid].Name != "" mgs.RLock()
group, ok := mgs.groups[gid]
mgs.RUnlock()
return ok && group.Name != ""
} }
// ? Allow two groups with the same name? // ? Allow two groups with the same name?
func (mgs *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod bool, isBanned bool) (int, error) { func (mgs *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod bool, isBanned bool) (gid int, err error) {
groupCreateMutex.Lock()
defer groupCreateMutex.Unlock()
var permstr = "{}" var permstr = "{}"
tx, err := db.Begin() tx, err := db.Begin()
if err != nil { if err != nil {
@ -143,7 +217,7 @@ func (mgs *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod
if err != nil { if err != nil {
return 0, err return 0, err
} }
var gid = int(gid64) gid = int(gid64)
var perms = BlankPerms var perms = BlankPerms
var blankForums []ForumPerms var blankForums []ForumPerms
@ -199,8 +273,10 @@ func (mgs *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod
isBanned = false isBanned = false
} }
mgs.groups = append(mgs.groups, &Group{gid, name, isMod, isAdmin, isBanned, tag, perms, []byte(permstr), pluginPerms, pluginPermsBytes, blankForums, blankIntList}) mgs.Lock()
mgs.groupCapCount++ mgs.groups[gid] = &Group{gid, name, isMod, isAdmin, isBanned, tag, perms, []byte(permstr), pluginPerms, pluginPermsBytes, blankForums, blankIntList}
mgs.groupCount++
mgs.Unlock()
for _, forum := range fdata { for _, forum := range fdata {
err = rebuildForumPermissions(forum.ID) err = rebuildForumPermissions(forum.ID)
@ -212,34 +288,62 @@ func (mgs *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod
return gid, nil return gid, nil
} }
// ! NOT CONCURRENT func (mgs *MemoryGroupStore) GetAll() (results []*Group, err error) {
func (mgs *MemoryGroupStore) GetAll() ([]*Group, error) { var i int
mgs.RLock()
results = make([]*Group, len(mgs.groups))
for _, group := range mgs.groups {
results[i] = group
i++
}
mgs.RUnlock()
sort.Sort(SortGroup(results))
return results, nil
}
func (mgs *MemoryGroupStore) GetAllMap() (map[int]*Group, error) {
mgs.RLock()
defer mgs.RUnlock()
return mgs.groups, nil 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 // ? - Set the lower and higher numbers to 0 to remove the bounds
// ? - Currently uses slicing for efficiency, so it might behave a little weirdly // TODO: Might be a little slow right now, maybe we can cache the groups in a slice or break the map up into chunks
func (mgs *MemoryGroupStore) GetRange(lower int, higher int) (groups []*Group, err error) { func (mgs *MemoryGroupStore) GetRange(lower int, higher int) (groups []*Group, err error) {
if lower == 0 && higher == 0 { if lower == 0 && higher == 0 {
return mgs.GetAll() return mgs.GetAll()
} else if lower == 0 { }
if lower == 0 {
if higher < 0 { if higher < 0 {
return nil, errors.New("higher may not be lower than 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 { } else if higher == 0 {
if lower < 0 { if lower < 0 {
return nil, errors.New("lower may not be lower than 0") return nil, errors.New("lower may not be lower than 0")
} }
groups = mgs.groups[lower:]
} }
mgs.RLock()
for gid, group := range mgs.groups {
if gid >= lower && (gid <= higher || higher == 0) {
groups = append(groups, group)
}
}
mgs.RUnlock()
sort.Sort(SortGroup(groups))
return groups, nil return groups, nil
} }
func (mgs *MemoryGroupStore) Length() int { func (mgs *MemoryGroupStore) Length() int {
return len(mgs.groups) mgs.RLock()
defer mgs.RUnlock()
return mgs.groupCount
}
func (mgs *MemoryGroupStore) GlobalCount() int {
mgs.RLock()
defer mgs.RUnlock()
return mgs.groupCount
} }

View File

@ -37,10 +37,13 @@ func TestUserStore(t *testing.T) {
initPlugins() initPlugins()
} }
users = NewMemoryUserStore(config.UserCacheCapacity) var err error
users, err = NewMemoryUserStore(config.UserCacheCapacity)
expectNilErr(t, err)
users.(UserCache).Flush() users.(UserCache).Flush()
userStoreTest(t, 2) userStoreTest(t, 2)
users = NewSQLUserStore() users, err = NewSQLUserStore()
expectNilErr(t, err)
userStoreTest(t, 3) userStoreTest(t, 3)
} }
func userStoreTest(t *testing.T, newUserID int) { func userStoreTest(t *testing.T, newUserID int) {
@ -453,9 +456,12 @@ func TestTopicStore(t *testing.T) {
initPlugins() initPlugins()
} }
topics = NewMemoryTopicStore(config.TopicCacheCapacity) var err error
topics, err = NewMemoryTopicStore(config.TopicCacheCapacity)
expectNilErr(t, err)
topicStoreTest(t) topicStoreTest(t)
topics = NewSQLTopicStore() topics, err = NewSQLTopicStore()
expectNilErr(t, err)
topicStoreTest(t) topicStoreTest(t)
} }
func topicStoreTest(t *testing.T) { func topicStoreTest(t *testing.T) {

View File

@ -1485,8 +1485,6 @@ func routePanelGroupsEditSubmit(w http.ResponseWriter, r *http.Request, user Use
originalRank = "Member" originalRank = "Member"
} }
groupUpdateMutex.Lock()
defer groupUpdateMutex.Unlock()
if rank != originalRank { if rank != originalRank {
if !user.Perms.EditGroupGlobalPerms { if !user.Perms.EditGroupGlobalPerms {
return LocalError("You need the EditGroupGlobalPerms permission to change the group type.", w, r, user) return LocalError("You need the EditGroupGlobalPerms permission to change the group type.", w, r, user)
@ -1517,12 +1515,12 @@ func routePanelGroupsEditSubmit(w http.ResponseWriter, r *http.Request, user Use
} }
} }
// TODO: Move this to *Group
_, err = updateGroupStmt.Exec(gname, gtag, gid) _, err = updateGroupStmt.Exec(gname, gtag, gid)
if err != nil { if err != nil {
return InternalError(err, w, r) return InternalError(err, w, r)
} }
group.Name = gname gstore.Reload(gid)
group.Tag = gtag
http.Redirect(w, r, "/panel/groups/edit/"+strconv.Itoa(gid), http.StatusSeeOther) http.Redirect(w, r, "/panel/groups/edit/"+strconv.Itoa(gid), http.StatusSeeOther)
return nil return nil

View File

@ -4,7 +4,6 @@ import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"log" "log"
"strconv"
"sync" "sync"
"./query_gen/lib" "./query_gen/lib"
@ -434,47 +433,9 @@ func rebuildForumPermissions(fid int) error {
forumPerms[gid][fid] = pperms forumPerms[gid][fid] = pperms
} }
groups, err := gstore.GetAll() return cascadePermSetToGroups(forumPerms, fids)
if err != nil {
return err
}
for _, group := range groups {
if dev.DebugMode {
log.Print("Updating the forum permissions for Group #" + strconv.Itoa(group.ID))
}
group.Forums = []ForumPerms{BlankForumPerms}
group.CanSee = []int{}
for _, ffid := range fids {
forumPerm, ok := forumPerms[group.ID][ffid]
if ok {
//log.Print("Overriding permissions for forum #" + strconv.Itoa(fid))
group.Forums = append(group.Forums, forumPerm)
} else {
//log.Print("Inheriting from default for forum #" + strconv.Itoa(fid))
forumPerm = BlankForumPerms
group.Forums = append(group.Forums, forumPerm)
}
if forumPerm.Overrides {
if forumPerm.ViewTopic {
group.CanSee = append(group.CanSee, ffid)
}
} else if group.Perms.ViewTopic {
group.CanSee = append(group.CanSee, ffid)
}
}
if dev.SuperDebug {
log.Printf("group.CanSee %+v\n", group.CanSee)
log.Printf("group.Forums %+v\n", group.Forums)
log.Print("len(group.CanSee)", len(group.CanSee))
log.Print("len(group.Forums)", len(group.Forums)) // This counts blank aka 0
}
}
return nil
} }
// ? - We could have buildForumPermissions and rebuildForumPermissions call a third function containing common logic?
func buildForumPermissions() error { func buildForumPermissions() error {
fids, err := fstore.GetAllIDs() fids, err := fstore.GetAllIDs()
if err != nil { if err != nil {
@ -496,6 +457,7 @@ func buildForumPermissions() error {
log.Print("forumPerms[gid][fid]") log.Print("forumPerms[gid][fid]")
} }
} }
// Temporarily store the forum perms in a map before transferring it to a much faster and thread-safe slice // Temporarily store the forum perms in a map before transferring it to a much faster and thread-safe slice
forumPerms = make(map[int]map[int]ForumPerms) forumPerms = make(map[int]map[int]ForumPerms)
for rows.Next() { for rows.Next() {
@ -529,6 +491,10 @@ func buildForumPermissions() error {
forumPerms[gid][fid] = pperms forumPerms[gid][fid] = pperms
} }
return cascadePermSetToGroups(forumPerms, fids)
}
func cascadePermSetToGroups(forumPerms map[int]map[int]ForumPerms, fids []int) error {
groups, err := gstore.GetAll() groups, err := gstore.GetAll()
if err != nil { if err != nil {
return err return err
@ -536,49 +502,54 @@ func buildForumPermissions() error {
for _, group := range groups { for _, group := range groups {
if dev.DebugMode { if dev.DebugMode {
log.Print("Adding the forum permissions for Group #" + strconv.Itoa(group.ID) + " - " + group.Name) log.Printf("Updating the forum permissions for Group #%d", group.ID)
} }
group.Forums = []ForumPerms{BlankForumPerms} group.Forums = []ForumPerms{BlankForumPerms}
group.CanSee = []int{} group.CanSee = []int{}
for _, fid := range fids { cascadePermSetToGroup(forumPerms, group, fids)
if dev.SuperDebug {
log.Printf("Forum #%+v\n", fid)
}
forumPerm, ok := forumPerms[group.ID][fid]
if ok {
// Override group perms
//log.Print("Overriding permissions for forum #" + strconv.Itoa(fid))
group.Forums = append(group.Forums, forumPerm)
} else {
// Inherit from Group
//log.Print("Inheriting from default for forum #" + strconv.Itoa(fid))
forumPerm = BlankForumPerms
group.Forums = append(group.Forums, forumPerm)
}
if forumPerm.Overrides {
if forumPerm.ViewTopic {
group.CanSee = append(group.CanSee, fid)
}
} else if group.Perms.ViewTopic {
group.CanSee = append(group.CanSee, fid)
}
if dev.SuperDebug {
log.Print("group.ID: ", group.ID)
log.Printf("forumPerm: %+v\n", forumPerm)
log.Print("group.CanSee: ", group.CanSee)
}
}
if dev.SuperDebug { if dev.SuperDebug {
log.Printf("group.CanSee %+v\n", group.CanSee) log.Printf("group.CanSee %+v\n", group.CanSee)
log.Printf("group.Forums %+v\n", group.Forums) log.Printf("group.Forums %+v\n", group.Forums)
log.Print("len(group.CanSee)", len(group.CanSee)) log.Print("len(group.CanSee): ", len(group.CanSee))
log.Print("len(group.Forums)", len(group.Forums)) // This counts blank aka 0 log.Print("len(group.Forums): ", len(group.Forums)) // This counts blank aka 0
} }
} }
return nil return nil
} }
func cascadePermSetToGroup(forumPerms map[int]map[int]ForumPerms, group *Group, fids []int) {
for _, fid := range fids {
if dev.SuperDebug {
log.Printf("Forum #%+v\n", fid)
}
forumPerm, ok := forumPerms[group.ID][fid]
if ok {
// Override group perms
//log.Print("Overriding permissions for forum #" + strconv.Itoa(fid))
group.Forums = append(group.Forums, forumPerm)
} else {
// Inherit from Group
//log.Print("Inheriting from default for forum #" + strconv.Itoa(fid))
forumPerm = BlankForumPerms
group.Forums = append(group.Forums, forumPerm)
}
if forumPerm.Overrides {
if forumPerm.ViewTopic {
group.CanSee = append(group.CanSee, fid)
}
} else if group.Perms.ViewTopic {
group.CanSee = append(group.CanSee, fid)
}
if dev.SuperDebug {
log.Print("group.ID: ", group.ID)
log.Printf("forumPerm: %+v\n", forumPerm)
log.Print("group.CanSee: ", group.CanSee)
}
}
}
func forumPermsToGroupForumPreset(fperms ForumPerms) string { func forumPermsToGroupForumPreset(fperms ForumPerms) string {
if !fperms.Overrides { if !fperms.Overrides {
return "default" return "default"

View File

@ -67,7 +67,7 @@ type GuildListPage struct {
Title string Title string
CurrentUser User CurrentUser User
Header *HeaderVars Header *HeaderVars
GroupList []*Guild GuildList []*Guild
} }
type GuildMemberListPage struct { type GuildMemberListPage struct {

View File

@ -18,6 +18,7 @@ type PluginMeta struct {
Settings string Settings string
Tag string Tag string
Skip bool // Skip this folder?
Main string // The main file Main string // The main file
Hooks map[string]string // Hooks mapped to functions Hooks map[string]string // Hooks mapped to functions
} }
@ -62,6 +63,9 @@ func InitPluginLangs() error {
if err != nil { if err != nil {
return err return err
} }
if plugin.Skip {
continue
}
if plugin.UName == "" { if plugin.UName == "" {
return errors.New("The UName field must not be blank on plugin '" + pluginItem + "'") return errors.New("The UName field must not be blank on plugin '" + pluginItem + "'")

View File

@ -2,12 +2,12 @@
<main> <main>
<div class="rowblock rowhead"> <div class="rowblock rowhead">
<div class="rowitem"><h1>Create Group</h1></div> <div class="rowitem"><h1>Create Guild</h1></div>
</div> </div>
<div class="rowblock"> <div class="rowblock">
<form action="/group/create/submit/" method="post"> <form action="/guild/create/submit/" method="post">
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>Group Name</a></div> <div class="formitem formlabel"><a>Guild Name</a></div>
<div class="formitem"><input name="group_name" type="text" placeholder="Group Name" /></div> <div class="formitem"><input name="group_name" type="text" placeholder="Group Name" /></div>
</div> </div>
<div class="formrow"> <div class="formrow">
@ -25,7 +25,7 @@
</div> </div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem"><button name="group_button" class="formbutton">Create Group</button></div> <div class="formitem"><button name="group_button" class="formbutton">Create Guild</button></div>
</div> </div>
</form> </form>
</div> </div>

View File

@ -1,10 +1,10 @@
{{template "header.html" . }} {{template "header.html" . }}
<main> <main>
<div class="rowblock opthead"> <div class="rowblock opthead">
<div class="rowitem"><a>Group List</a></div> <div class="rowitem"><a>Guild List</a></div>
</div> </div>
<div class="rowblock"> <div class="rowblock">
{{range .GroupList}}<div class="rowitem datarow"> {{range .GuildList}}<div class="rowitem datarow">
<span style="float: left;"> <span style="float: left;">
<a href="{{.Link}}" style="">{{.Name}}</a> <a href="{{.Link}}" style="">{{.Name}}</a>
<br /><span class="rowsmall">{{.Desc}}</span> <br /><span class="rowsmall">{{.Desc}}</span>
@ -15,7 +15,7 @@
</span> </span>
<div style="clear: both;"></div> <div style="clear: both;"></div>
</div> </div>
{{else}}<div class="rowitem passive">There aren't any visible groups.</div>{{end}} {{else}}<div class="rowitem passive">There aren't any visible guilds.</div>{{end}}
</div> </div>
</main> </main>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1,19 +1,20 @@
{{template "header.html" . }} {{template "header.html" . }}
{{/** TODO: Move this into a CSS file **/}} {{/** TODO: Move this into a per-theme CSS file **/}}
{{template "socialgroups_css.html" . }} {{template "guilds_css.html" . }}
{{/** TODO: Add <link> next / prev bits **/}}
{{/** TODO: Port the page template functions to the template interpreter **/}} {{/** TODO: Port the page template functions to the template interpreter **/}}
{{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" href="/group/members/{{.SocialGroup.ID}}?page={{subtract .Page 1}}">&lt;</a></div>{{end}} {{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" href="/guild/members/{{.Guild.ID}}?page={{subtract .Page 1}}">&lt;</a></div>{{end}}
{{if ne .LastPage .Page}}<link rel="prerender" href="/group/members/{{.SocialGroup.ID}}?page={{add .Page 1}}" /> {{if ne .LastPage .Page}}<link rel="prerender" href="/guild/members/{{.Guild.ID}}?page={{add .Page 1}}" />
<div id="nextFloat" class="next_button"><a class="next_link" href="/group/members/{{.SocialGroup.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}} <div id="nextFloat" class="next_button"><a class="next_link" href="/guild/members/{{.Guild.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}}
<div class="sgBackdrop"> <div class="sgBackdrop">
<nav class="miniMenu"> <nav class="miniMenu">
<div class="menuItem"><a href="/group/{{.SocialGroup.ID}}">{{.SocialGroup.Name}}</a></div> <div class="menuItem"><a href="/guild/{{.Guild.ID}}">{{.Guild.Name}}</a></div>
<div class="menuItem"><a href="#">About</a></div> <div class="menuItem"><a href="#">About</a></div>
<div class="menuItem"><a href="/group/members/{{.SocialGroup.ID}}">Members</a></div> <div class="menuItem"><a href="/guild/members/{{.Guild.ID}}">Members</a></div>
<div class="menuItem rightMenu"><a href="#">Edit</a></div> <div class="menuItem rightMenu"><a href="#">Edit</a></div>
<div class="menuItem rightMenu"><a href="/group/join/{{.SocialGroup.ID}}">Join</a></div> <div class="menuItem rightMenu"><a href="/guild/join/{{.Guild.ID}}">Join</a></div>
</nav> </nav>
<div style="clear: both;"></div> <div style="clear: both;"></div>
</div> </div>
@ -25,7 +26,7 @@
</span> </span>
<span> <span>
<a class="rowtopic" href="{{.Link}}">{{.User.Name}}</a> <a class="rowtopic" href="{{.Link}}">{{.User.Name}}</a>
{{/** Use this for badges instead of rank? Both? Group Titles? **/}} {{/** Use this for badges instead of rank? Both? Guild Titles? **/}}
<br /><span class="rowsmall postCount">{{.PostCount}} posts</span> <br /><span class="rowsmall postCount">{{.PostCount}} posts</span>
</span> </span>
</div> </div>

View File

@ -3,18 +3,18 @@
{{template "socialgroups_css.html" . }} {{template "socialgroups_css.html" . }}
{{/** TODO: Port the page template functions to the template interpreter **/}} {{/** TODO: Port the page template functions to the template interpreter **/}}
{{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" href="/group/{{.SocialGroup.ID}}?page={{subtract .Page 1}}">&lt;</a></div>{{end}} {{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" href="/guild/{{.Guild.ID}}?page={{subtract .Page 1}}">&lt;</a></div>{{end}}
{{if ne .LastPage .Page}}<link rel="prerender" href="/group/{{.SocialGroup.ID}}?page={{add .Page 1}}" /> {{if ne .LastPage .Page}}<link rel="prerender" href="/guild/{{.Guild.ID}}?page={{add .Page 1}}" />
<div id="nextFloat" class="next_button"><a class="next_link" href="/group/{{.SocialGroup.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}} <div id="nextFloat" class="next_button"><a class="next_link" href="/guild/{{.Guild.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}}
<div class="sgBackdrop"> <div class="sgBackdrop">
<nav class="miniMenu"> <nav class="miniMenu">
<div class="menuItem"><a href="/group/{{.SocialGroup.ID}}">{{.SocialGroup.Name}}</a></div> <div class="menuItem"><a href="/guild/{{.Guild.ID}}">{{.Guild.Name}}</a></div>
<div class="menuItem"><a href="#">About</a></div> <div class="menuItem"><a href="#">About</a></div>
<div class="menuItem"><a href="/group/members/{{.SocialGroup.ID}}">Members</a></div> <div class="menuItem"><a href="/guild/members/{{.Guild.ID}}">Members</a></div>
<div class="menuItem rightMenu"><a href="#">Edit</a></div> <div class="menuItem rightMenu"><a href="#">Edit</a></div>
<div class="menuItem rightMenu"><a href="/topics/create/{{.Forum.ID}}">Reply</a></div> <div class="menuItem rightMenu"><a href="/topics/create/{{.Forum.ID}}">Reply</a></div>
<div class="menuItem rightMenu"><a href="/group/join/{{.SocialGroup.ID}}">Join</a></div> <div class="menuItem rightMenu"><a href="/guild/join/{{.Guild.ID}}">Join</a></div>
</nav> </nav>
<div style="clear: both;"></div> <div style="clear: both;"></div>
</div> </div>

View File

@ -9,7 +9,6 @@ package main
import ( import (
"database/sql" "database/sql"
"errors" "errors"
"log"
"strings" "strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -63,18 +62,18 @@ type MemoryTopicStore struct {
} }
// NewMemoryTopicStore gives you a new instance of MemoryTopicStore // NewMemoryTopicStore gives you a new instance of MemoryTopicStore
func NewMemoryTopicStore(capacity int) *MemoryTopicStore { func NewMemoryTopicStore(capacity int) (*MemoryTopicStore, error) {
getStmt, err := qgen.Builder.SimpleSelect("topics", "title, content, createdBy, createdAt, lastReplyAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data", "tid = ?", "", "") getStmt, err := qgen.Builder.SimpleSelect("topics", "title, content, createdBy, createdAt, lastReplyAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data", "tid = ?", "", "")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
existsStmt, err := qgen.Builder.SimpleSelect("topics", "tid", "tid = ?", "", "") existsStmt, err := qgen.Builder.SimpleSelect("topics", "tid", "tid = ?", "", "")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
topicCountStmt, err := qgen.Builder.SimpleCount("topics", "", "") topicCountStmt, err := qgen.Builder.SimpleCount("topics", "", "")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
return &MemoryTopicStore{ return &MemoryTopicStore{
items: make(map[int]*Topic), items: make(map[int]*Topic),
@ -82,7 +81,7 @@ func NewMemoryTopicStore(capacity int) *MemoryTopicStore {
get: getStmt, get: getStmt,
exists: existsStmt, exists: existsStmt,
topicCount: topicCountStmt, topicCount: topicCountStmt,
} }, nil
} }
func (mts *MemoryTopicStore) CacheGet(id int) (*Topic, error) { func (mts *MemoryTopicStore) CacheGet(id int) (*Topic, error) {
@ -267,24 +266,24 @@ type SQLTopicStore struct {
topicCount *sql.Stmt topicCount *sql.Stmt
} }
func NewSQLTopicStore() *SQLTopicStore { func NewSQLTopicStore() (*SQLTopicStore, error) {
getStmt, err := qgen.Builder.SimpleSelect("topics", "title, content, createdBy, createdAt, lastReplyAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data", "tid = ?", "", "") getStmt, err := qgen.Builder.SimpleSelect("topics", "title, content, createdBy, createdAt, lastReplyAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data", "tid = ?", "", "")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
existsStmt, err := qgen.Builder.SimpleSelect("topics", "tid", "tid = ?", "", "") existsStmt, err := qgen.Builder.SimpleSelect("topics", "tid", "tid = ?", "", "")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
topicCountStmt, err := qgen.Builder.SimpleCount("topics", "", "") topicCountStmt, err := qgen.Builder.SimpleCount("topics", "", "")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
return &SQLTopicStore{ return &SQLTopicStore{
get: getStmt, get: getStmt,
exists: existsStmt, exists: existsStmt,
topicCount: topicCountStmt, topicCount: topicCountStmt,
} }, nil
} }
func (sts *SQLTopicStore) Get(id int) (*Topic, error) { func (sts *SQLTopicStore) Get(id int) (*Topic, error) {

14
user.go
View File

@ -12,6 +12,7 @@ import (
"database/sql" "database/sql"
"errors" "errors"
"strconv" "strconv"
"strings"
"time" "time"
"./query_gen/lib" "./query_gen/lib"
@ -65,6 +66,19 @@ type Email struct {
Token string Token string
} }
func (user *User) Init() {
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), user.ID)
user.Tag = gstore.DirtyGet(user.Group).Tag
user.initPerms()
}
func (user *User) Ban(duration time.Duration, issuedBy int) error { func (user *User) Ban(duration time.Duration, issuedBy int) error {
return user.ScheduleGroupUpdate(banGroup, issuedBy, duration) return user.ScheduleGroupUpdate(banGroup, issuedBy, duration)
} }

View File

@ -5,7 +5,6 @@ import (
"errors" "errors"
"log" "log"
"strconv" "strconv"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -56,32 +55,32 @@ type MemoryUserStore struct {
} }
// NewMemoryUserStore gives you a new instance of MemoryUserStore // NewMemoryUserStore gives you a new instance of MemoryUserStore
func NewMemoryUserStore(capacity int) *MemoryUserStore { func NewMemoryUserStore(capacity int) (*MemoryUserStore, error) {
getStmt, err := qgen.Builder.SimpleSelect("users", "name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid = ?", "", "") getStmt, err := qgen.Builder.SimpleSelect("users", "name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid = ?", "", "")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
existsStmt, err := qgen.Builder.SimpleSelect("users", "uid", "uid = ?", "", "") existsStmt, err := qgen.Builder.SimpleSelect("users", "uid", "uid = ?", "", "")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
// Add an admin version of register_stmt with more flexibility? // Add an admin version of register_stmt with more flexibility?
// create_account_stmt, err = db.Prepare("INSERT INTO // create_account_stmt, err = db.Prepare("INSERT INTO
registerStmt, err := qgen.Builder.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt", "?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()") registerStmt, err := qgen.Builder.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt", "?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
usernameExistsStmt, err := qgen.Builder.SimpleSelect("users", "name", "name = ?", "", "") usernameExistsStmt, err := qgen.Builder.SimpleSelect("users", "name", "name = ?", "", "")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
userCountStmt, err := qgen.Builder.SimpleCount("users", "", "") userCountStmt, err := qgen.Builder.SimpleCount("users", "", "")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
return &MemoryUserStore{ return &MemoryUserStore{
@ -92,7 +91,7 @@ func NewMemoryUserStore(capacity int) *MemoryUserStore {
register: registerStmt, register: registerStmt,
usernameExists: usernameExistsStmt, usernameExists: usernameExistsStmt,
userCount: userCountStmt, userCount: userCountStmt,
} }, nil
} }
func (mus *MemoryUserStore) CacheGet(id int) (*User, error) { func (mus *MemoryUserStore) CacheGet(id int) (*User, error) {
@ -124,17 +123,7 @@ func (mus *MemoryUserStore) Get(id int) (*User, error) {
user = &User{ID: id, Loggedin: true} user = &User{ID: id, Loggedin: true}
err := mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup) err := mus.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)
// TODO: Add an init method to User rather than writing this same bit of code over and over user.Init()
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 = gstore.DirtyGet(user.Group).Tag
user.initPerms()
if err == nil { if err == nil {
mus.CacheSet(user) mus.CacheSet(user)
} }
@ -203,18 +192,8 @@ func (mus *MemoryUserStore) BulkGetMap(ids []int) (list map[int]*User, err error
return nil, err return nil, err
} }
// TODO: Add an init method to User rather than writing this same bit of code over and over
// Initialise the user // Initialise the user
if user.Avatar != "" { user.Init()
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), user.ID)
user.Tag = gstore.DirtyGet(user.Group).Tag
user.initPerms()
// Add it to the cache... // Add it to the cache...
_ = mus.CacheSet(user) _ = mus.CacheSet(user)
@ -255,17 +234,7 @@ func (mus *MemoryUserStore) BypassGet(id int) (*User, error) {
user := &User{ID: id, Loggedin: true} user := &User{ID: id, Loggedin: true}
err := mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup) err := mus.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)
// TODO: Add an init method to User rather than writing this same bit of code over and over user.Init()
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 = gstore.DirtyGet(user.Group).Tag
user.initPerms()
return user, err return user, err
} }
@ -277,17 +246,7 @@ func (mus *MemoryUserStore) Reload(id int) error {
return err return err
} }
// TODO: Add an init method to User rather than writing this same bit of code over and over user.Init()
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 = gstore.DirtyGet(user.Group).Tag
user.initPerms()
_ = mus.CacheSet(user) _ = mus.CacheSet(user)
return nil return nil
} }
@ -425,32 +384,32 @@ type SQLUserStore struct {
userCount *sql.Stmt userCount *sql.Stmt
} }
func NewSQLUserStore() *SQLUserStore { func NewSQLUserStore() (*SQLUserStore, error) {
getStmt, err := qgen.Builder.SimpleSelect("users", "name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid = ?", "", "") getStmt, err := qgen.Builder.SimpleSelect("users", "name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid = ?", "", "")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
existsStmt, err := qgen.Builder.SimpleSelect("users", "uid", "uid = ?", "", "") existsStmt, err := qgen.Builder.SimpleSelect("users", "uid", "uid = ?", "", "")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
// Add an admin version of register_stmt with more flexibility? // Add an admin version of register_stmt with more flexibility?
// create_account_stmt, err = db.Prepare("INSERT INTO // create_account_stmt, err = db.Prepare("INSERT INTO
registerStmt, err := qgen.Builder.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt", "?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()") registerStmt, err := qgen.Builder.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt", "?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
usernameExistsStmt, err := qgen.Builder.SimpleSelect("users", "name", "name = ?", "", "") usernameExistsStmt, err := qgen.Builder.SimpleSelect("users", "name", "name = ?", "", "")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
userCountStmt, err := qgen.Builder.SimpleCount("users", "", "") userCountStmt, err := qgen.Builder.SimpleCount("users", "", "")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
return &SQLUserStore{ return &SQLUserStore{
@ -459,23 +418,14 @@ func NewSQLUserStore() *SQLUserStore {
register: registerStmt, register: registerStmt,
usernameExists: usernameExistsStmt, usernameExists: usernameExistsStmt,
userCount: userCountStmt, userCount: userCountStmt,
} }, nil
} }
func (mus *SQLUserStore) Get(id int) (*User, error) { func (mus *SQLUserStore) Get(id int) (*User, error) {
user := &User{ID: id, Loggedin: true} user := &User{ID: id, Loggedin: true}
err := mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup) err := mus.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 != "" { user.Init()
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 = gstore.DirtyGet(user.Group).Tag
user.initPerms()
return user, err return user, err
} }
@ -508,16 +458,7 @@ func (mus *SQLUserStore) BulkGetMap(ids []int) (list map[int]*User, err error) {
} }
// Initialise the user // Initialise the user
if user.Avatar != "" { user.Init()
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), user.ID)
user.Tag = gstore.DirtyGet(user.Group).Tag
user.initPerms()
// Add it to the list to be returned // Add it to the list to be returned
list[user.ID] = user list[user.ID] = user
@ -530,16 +471,7 @@ func (mus *SQLUserStore) BypassGet(id int) (*User, error) {
user := &User{ID: id, Loggedin: true} user := &User{ID: id, Loggedin: true}
err := mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup) err := mus.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 != "" { user.Init()
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 = gstore.DirtyGet(user.Group).Tag
user.initPerms()
return user, err return user, err
} }