github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/common/group_store.go (about)

     1  /* Under Heavy Construction */
     2  package common
     3  
     4  import (
     5  	"database/sql"
     6  	"encoding/json"
     7  	"errors"
     8  	"log"
     9  	"sort"
    10  	"strconv"
    11  	"sync"
    12  
    13  	qgen "github.com/Azareal/Gosora/query_gen"
    14  )
    15  
    16  var Groups GroupStore
    17  
    18  // ? - We could fallback onto the database when an item can't be found in the cache?
    19  type GroupStore interface {
    20  	LoadGroups() error
    21  	DirtyGet(id int) *Group
    22  	Get(id int) (*Group, error)
    23  	GetCopy(id int) (Group, error)
    24  	Exists(id int) bool
    25  	Create(name, tag string, isAdmin, isMod, isBanned bool) (id int, err error)
    26  	GetAll() ([]*Group, error)
    27  	GetRange(lower, higher int) ([]*Group, error)
    28  	Reload(id int) error // ? - Should we move this to GroupCache? It might require us to do some unnecessary casting though
    29  	Count() int
    30  }
    31  
    32  type GroupCache interface {
    33  	CacheSet(g *Group) error
    34  	SetCanSee(gid int, canSee []int) error
    35  	CacheAdd(g *Group) error
    36  	Length() int
    37  }
    38  
    39  type MemoryGroupStore struct {
    40  	groups     map[int]*Group // TODO: Use a sync.Map instead of a map?
    41  	groupCount int
    42  	getAll     *sql.Stmt
    43  	get        *sql.Stmt
    44  	count      *sql.Stmt
    45  	userCount  *sql.Stmt
    46  
    47  	sync.RWMutex
    48  }
    49  
    50  func NewMemoryGroupStore() (*MemoryGroupStore, error) {
    51  	acc := qgen.NewAcc()
    52  	ug := "users_groups"
    53  	return &MemoryGroupStore{
    54  		groups:     make(map[int]*Group),
    55  		groupCount: 0,
    56  		getAll:     acc.Select(ug).Columns("gid,name,permissions,plugin_perms,is_mod,is_admin,is_banned,tag").Prepare(),
    57  		get:        acc.Select(ug).Columns("name,permissions,plugin_perms,is_mod,is_admin,is_banned,tag").Where("gid=?").Prepare(),
    58  		count:      acc.Count(ug).Prepare(),
    59  		userCount:  acc.Count("users").Where("group=?").Prepare(),
    60  	}, acc.FirstError()
    61  }
    62  
    63  // TODO: Move this query from the global stmt store into this store
    64  func (s *MemoryGroupStore) LoadGroups() error {
    65  	s.Lock()
    66  	defer s.Unlock()
    67  	s.groups[0] = &Group{ID: 0, Name: "Unknown"}
    68  
    69  	rows, err := s.getAll.Query()
    70  	if err != nil {
    71  		return err
    72  	}
    73  	defer rows.Close()
    74  
    75  	i := 1
    76  	for ; rows.Next(); i++ {
    77  		g := &Group{ID: 0}
    78  		err := rows.Scan(&g.ID, &g.Name, &g.PermissionsText, &g.PluginPermsText, &g.IsMod, &g.IsAdmin, &g.IsBanned, &g.Tag)
    79  		if err != nil {
    80  			return err
    81  		}
    82  
    83  		err = s.initGroup(g)
    84  		if err != nil {
    85  			return err
    86  		}
    87  		s.groups[g.ID] = g
    88  	}
    89  	if err = rows.Err(); err != nil {
    90  		return err
    91  	}
    92  	s.groupCount = i
    93  
    94  	DebugLog("Binding the Not Loggedin Group")
    95  	GuestPerms = s.dirtyGetUnsafe(6).Perms // ! Race?
    96  	TopicListThaw.Thaw()
    97  	return nil
    98  }
    99  
   100  // TODO: Hit the database when the item isn't in memory
   101  func (s *MemoryGroupStore) dirtyGetUnsafe(id int) *Group {
   102  	group, ok := s.groups[id]
   103  	if !ok {
   104  		return &blankGroup
   105  	}
   106  	return group
   107  }
   108  
   109  // TODO: Hit the database when the item isn't in memory
   110  func (s *MemoryGroupStore) DirtyGet(id int) *Group {
   111  	s.RLock()
   112  	group, ok := s.groups[id]
   113  	s.RUnlock()
   114  	if !ok {
   115  		return &blankGroup
   116  	}
   117  	return group
   118  }
   119  
   120  // TODO: Hit the database when the item isn't in memory
   121  func (s *MemoryGroupStore) Get(id int) (*Group, error) {
   122  	s.RLock()
   123  	group, ok := s.groups[id]
   124  	s.RUnlock()
   125  	if !ok {
   126  		return nil, ErrNoRows
   127  	}
   128  	return group, nil
   129  }
   130  
   131  // TODO: Hit the database when the item isn't in memory
   132  func (s *MemoryGroupStore) GetCopy(id int) (Group, error) {
   133  	s.RLock()
   134  	group, ok := s.groups[id]
   135  	s.RUnlock()
   136  	if !ok {
   137  		return blankGroup, ErrNoRows
   138  	}
   139  	return *group, nil
   140  }
   141  
   142  func (s *MemoryGroupStore) Reload(id int) error {
   143  	// TODO: Reload this data too
   144  	g, e := s.Get(id)
   145  	if e != nil {
   146  		LogError(errors.New("can't get cansee data for group #" + strconv.Itoa(id)))
   147  		return nil
   148  	}
   149  	canSee := g.CanSee
   150  
   151  	g = &Group{ID: id, CanSee: canSee}
   152  	e = s.get.QueryRow(id).Scan(&g.Name, &g.PermissionsText, &g.PluginPermsText, &g.IsMod, &g.IsAdmin, &g.IsBanned, &g.Tag)
   153  	if e != nil {
   154  		return e
   155  	}
   156  	if e = s.initGroup(g); e != nil {
   157  		LogError(e)
   158  		return nil
   159  	}
   160  
   161  	s.CacheSet(g)
   162  	TopicListThaw.Thaw()
   163  	return nil
   164  }
   165  
   166  func (s *MemoryGroupStore) initGroup(g *Group) error {
   167  	e := json.Unmarshal(g.PermissionsText, &g.Perms)
   168  	if e != nil {
   169  		log.Printf("g: %+v\n", g)
   170  		log.Print("bad group perms: ", g.PermissionsText)
   171  		return e
   172  	}
   173  	DebugLogf(g.Name+": %+v\n", g.Perms)
   174  
   175  	e = json.Unmarshal(g.PluginPermsText, &g.PluginPerms)
   176  	if e != nil {
   177  		log.Printf("g: %+v\n", g)
   178  		log.Print("bad group plugin perms: ", g.PluginPermsText)
   179  		return e
   180  	}
   181  	DebugLogf(g.Name+": %+v\n", g.PluginPerms)
   182  
   183  	//group.Perms.ExtData = make(map[string]bool)
   184  	// TODO: Can we optimise the bit where this cascades down to the user now?
   185  	if g.IsAdmin || g.IsMod {
   186  		g.IsBanned = false
   187  	}
   188  
   189  	e = s.userCount.QueryRow(g.ID).Scan(&g.UserCount)
   190  	if e != sql.ErrNoRows {
   191  		return e
   192  	}
   193  	return nil
   194  }
   195  
   196  func (s *MemoryGroupStore) SetCanSee(gid int, canSee []int) error {
   197  	s.Lock()
   198  	group, ok := s.groups[gid]
   199  	if !ok {
   200  		s.Unlock()
   201  		return nil
   202  	}
   203  	ngroup := &Group{}
   204  	*ngroup = *group
   205  	ngroup.CanSee = canSee
   206  	s.groups[group.ID] = ngroup
   207  	s.Unlock()
   208  	return nil
   209  }
   210  
   211  func (s *MemoryGroupStore) CacheSet(g *Group) error {
   212  	s.Lock()
   213  	s.groups[g.ID] = g
   214  	s.Unlock()
   215  	return nil
   216  }
   217  
   218  // TODO: Hit the database when the item isn't in memory
   219  func (s *MemoryGroupStore) Exists(id int) bool {
   220  	s.RLock()
   221  	group, ok := s.groups[id]
   222  	s.RUnlock()
   223  	return ok && group.Name != ""
   224  }
   225  
   226  // ? Allow two groups with the same name?
   227  // TODO: Refactor this
   228  func (s *MemoryGroupStore) Create(name, tag string, isAdmin, isMod, isBanned bool) (gid int, err error) {
   229  	permstr := "{}"
   230  	tx, err := qgen.Builder.Begin()
   231  	if err != nil {
   232  		return 0, err
   233  	}
   234  	defer tx.Rollback()
   235  
   236  	insertTx, err := qgen.Builder.SimpleInsertTx(tx, "users_groups", "name,tag,is_admin,is_mod,is_banned,permissions,plugin_perms", "?,?,?,?,?,?,'{}'")
   237  	if err != nil {
   238  		return 0, err
   239  	}
   240  	res, err := insertTx.Exec(name, tag, isAdmin, isMod, isBanned, permstr)
   241  	if err != nil {
   242  		return 0, err
   243  	}
   244  	gid64, err := res.LastInsertId()
   245  	if err != nil {
   246  		return 0, err
   247  	}
   248  	gid = int(gid64)
   249  
   250  	perms := BlankPerms
   251  	blankIntList := []int{}
   252  	pluginPerms := make(map[string]bool)
   253  	pluginPermsBytes := []byte("{}")
   254  	GetHookTable().Vhook("create_group_preappend", &pluginPerms, &pluginPermsBytes)
   255  
   256  	// Generate the forum permissions based on the presets...
   257  	forums, err := Forums.GetAll()
   258  	if err != nil {
   259  		return 0, err
   260  	}
   261  
   262  	presetSet := make(map[int]string)
   263  	permSet := make(map[int]*ForumPerms)
   264  	for _, f := range forums {
   265  		var thePreset string
   266  		switch {
   267  		case isAdmin:
   268  			thePreset = "admins"
   269  		case isMod:
   270  			thePreset = "staff"
   271  		case isBanned:
   272  			thePreset = "banned"
   273  		default:
   274  			thePreset = "members"
   275  		}
   276  
   277  		permmap := PresetToPermmap(f.Preset)
   278  		permItem := permmap[thePreset]
   279  		permItem.Overrides = true
   280  
   281  		permSet[f.ID] = permItem
   282  		presetSet[f.ID] = f.Preset
   283  	}
   284  
   285  	err = ReplaceForumPermsForGroupTx(tx, gid, presetSet, permSet)
   286  	if err != nil {
   287  		return 0, err
   288  	}
   289  	err = tx.Commit()
   290  	if err != nil {
   291  		return 0, err
   292  	}
   293  
   294  	// TODO: Can we optimise the bit where this cascades down to the user now?
   295  	if isAdmin || isMod {
   296  		isBanned = false
   297  	}
   298  
   299  	s.CacheAdd(&Group{gid, name, isMod, isAdmin, isBanned, tag, perms, []byte(permstr), pluginPerms, pluginPermsBytes, blankIntList, 0})
   300  
   301  	TopicListThaw.Thaw()
   302  	return gid, FPStore.ReloadAll()
   303  	//return gid, TopicList.RebuildPermTree()
   304  }
   305  
   306  func (s *MemoryGroupStore) CacheAdd(g *Group) error {
   307  	s.Lock()
   308  	s.groups[g.ID] = g
   309  	s.groupCount++
   310  	s.Unlock()
   311  	return nil
   312  }
   313  
   314  func (s *MemoryGroupStore) GetAll() (results []*Group, err error) {
   315  	var i int
   316  	s.RLock()
   317  	results = make([]*Group, len(s.groups))
   318  	for _, group := range s.groups {
   319  		results[i] = group
   320  		i++
   321  	}
   322  	s.RUnlock()
   323  	sort.Sort(SortGroup(results))
   324  	return results, nil
   325  }
   326  
   327  func (s *MemoryGroupStore) GetAllMap() (map[int]*Group, error) {
   328  	s.RLock()
   329  	defer s.RUnlock()
   330  	return s.groups, nil
   331  }
   332  
   333  // ? - Set the lower and higher numbers to 0 to remove the bounds
   334  // TODO: Might be a little slow right now, maybe we can cache the groups in a slice or break the map up into chunks
   335  func (s *MemoryGroupStore) GetRange(lower, higher int) (groups []*Group, err error) {
   336  	if lower == 0 && higher == 0 {
   337  		return s.GetAll()
   338  	}
   339  
   340  	// TODO: Simplify these four conditionals into two
   341  	if lower == 0 {
   342  		if higher < 0 {
   343  			return nil, errors.New("higher may not be lower than 0")
   344  		}
   345  	} else if higher == 0 {
   346  		if lower < 0 {
   347  			return nil, errors.New("lower may not be lower than 0")
   348  		}
   349  	}
   350  
   351  	s.RLock()
   352  	for gid, group := range s.groups {
   353  		if gid >= lower && (gid <= higher || higher == 0) {
   354  			groups = append(groups, group)
   355  		}
   356  	}
   357  	s.RUnlock()
   358  	sort.Sort(SortGroup(groups))
   359  
   360  	return groups, nil
   361  }
   362  
   363  func (s *MemoryGroupStore) Length() int {
   364  	s.RLock()
   365  	defer s.RUnlock()
   366  	return s.groupCount
   367  }
   368  
   369  func (s *MemoryGroupStore) Count() (count int) {
   370  	err := s.count.QueryRow().Scan(&count)
   371  	if err != nil {
   372  		LogError(err)
   373  	}
   374  	return count
   375  }