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 }