github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/routes/topic_list.go (about) 1 package routes 2 3 import ( 4 "database/sql" 5 "log" 6 "net/http" 7 "strconv" 8 "strings" 9 10 c "github.com/Azareal/Gosora/common" 11 "github.com/Azareal/Gosora/common/phrases" 12 ) 13 14 func wsTopicList(topicList []*c.TopicsRow, lastPage int) *c.WsTopicList { 15 wsTopicList := make([]*c.WsTopicsRow, len(topicList)) 16 for i, tr := range topicList { 17 wsTopicList[i] = tr.WebSockets() 18 } 19 return &c.WsTopicList{wsTopicList, lastPage, 0} 20 } 21 22 func wsTopicList2(topicList []*c.TopicsRow, u *c.User, fps map[int]c.QuickTools, lastPage int) *c.WsTopicList { 23 wsTopicList := make([]*c.WsTopicsRow, len(topicList)) 24 for i, t := range topicList { 25 var canMod bool 26 if fps == nil { 27 canMod = true 28 } else { 29 quickTools := fps[t.ParentID] 30 canMod = t.CreatedBy == u.ID || quickTools.CanDelete || quickTools.CanLock || quickTools.CanMove 31 } 32 wsTopicList[i] = t.WebSockets2(canMod) 33 } 34 return &c.WsTopicList{wsTopicList, lastPage, 0} 35 } 36 37 func TopicList(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header) c.RouteError { 38 /*skip, rerr := h.Hooks.VhookSkippable("route_topic_list_start", w, r, u, h) 39 if skip || rerr != nil { 40 return rerr 41 }*/ 42 skip, rerr := c.H_route_topic_list_start_hook(h.Hooks, w, r, u, h) 43 if skip || rerr != nil { 44 return rerr 45 } 46 return TopicListCommon(w, r, u, h, "lastupdated", 0) 47 } 48 49 func TopicListMostViewed(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header) c.RouteError { 50 skip, rerr := h.Hooks.VhookSkippable("route_topic_list_mostviewed_start", w, r, u, h) 51 if skip || rerr != nil { 52 return rerr 53 } 54 return TopicListCommon(w, r, u, h, "mostviewed", c.TopicListMostViewed) 55 } 56 57 func TopicListWeekViews(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header) c.RouteError { 58 skip, rerr := h.Hooks.VhookSkippable("route_topic_list_weekviews_start", w, r, u, h) 59 if skip || rerr != nil { 60 return rerr 61 } 62 return TopicListCommon(w, r, u, h, "weekviews", c.TopicListWeekViews) 63 } 64 65 // TODO: Implement search 66 func TopicListCommon(w http.ResponseWriter, r *http.Request, user *c.User, h *c.Header, torder string, tsorder int) c.RouteError { 67 h.Title = phrases.GetTitlePhrase("topics") 68 h.Zone = "topics" 69 h.Path = "/topics/" 70 h.MetaDesc = h.Settings["meta_desc"].(string) 71 72 group, err := c.Groups.Get(user.Group) 73 if err != nil { 74 log.Printf("Group #%d doesn't exist despite being used by c.User #%d", user.Group, user.ID) 75 return c.LocalError("Something weird happened", w, r, user) 76 } 77 78 var forumList []c.Forum 79 // Get the current page 80 page, _ := strconv.Atoi(r.FormValue("page")) 81 sfids := r.FormValue("fids") 82 var fids []int 83 if sfids != "" { 84 for _, sfid := range strings.Split(sfids, ",") { 85 fid, err := strconv.Atoi(sfid) 86 if err != nil { 87 return c.LocalError("Invalid fid", w, r, user) 88 } 89 fids = append(fids, fid) 90 } 91 if len(fids) == 1 { 92 f, err := c.Forums.Get(fids[0]) 93 if err != nil { 94 return c.LocalError("Invalid fid forum", w, r, user) 95 } 96 h.Title = f.Name 97 h.ZoneID = f.ID 98 forumList = append(forumList, *f) 99 } 100 } 101 102 // TODO: Allow multiple forums in searches 103 // TODO: Simplify this block after initially landing search 104 var topicList []*c.TopicsRow 105 var pagi c.Paginator 106 var canDelete, ccanDelete, canLock, ccanLock, canMove, ccanMove bool 107 q := r.FormValue("q") 108 if q != "" && c.RepliesSearch != nil { 109 var canSee []int 110 if user.IsSuperAdmin { 111 canSee, err = c.Forums.GetAllVisibleIDs() 112 if err != nil { 113 return c.InternalError(err, w, r) 114 } 115 } else { 116 canSee = group.CanSee 117 } 118 119 var cfids []int 120 if len(fids) > 0 { 121 inSlice := func(haystack []int, needle int) bool { 122 for _, it := range haystack { 123 if needle == it { 124 return true 125 } 126 } 127 return false 128 } 129 for _, fid := range fids { 130 if inSlice(canSee, fid) { 131 f := c.Forums.DirtyGet(fid) 132 if f.Name != "" && f.Active && (f.ParentType == "" || f.ParentType == "forum") && f.TopicCount != 0 { 133 // TODO: Add a hook here for plugin_guilds? 134 cfids = append(cfids, fid) 135 } 136 } 137 } 138 } else { 139 cfids = canSee 140 } 141 142 tids, err := c.RepliesSearch.Query(q, cfids) 143 if err != nil && err != sql.ErrNoRows { 144 return c.InternalError(err, w, r) 145 } 146 //log.Printf("tids %+v\n", tids) 147 // TODO: Handle the case where there aren't any items... 148 // TODO: Add a BulkGet method which returns a slice? 149 tMap, err := c.Topics.BulkGetMap(tids) 150 if err != nil { 151 return c.InternalError(err, w, r) 152 } 153 // TODO: Cache emptied map across requests with sync pool 154 reqUserList := make(map[int]bool) 155 for _, t := range tMap { 156 reqUserList[t.CreatedBy] = true 157 reqUserList[t.LastReplyBy] = true 158 topicList = append(topicList, t.TopicsRow()) 159 } 160 //fmt.Printf("reqUserList %+v\n", reqUserList) 161 162 // Convert the user ID map to a slice, then bulk load the users 163 idSlice := make([]int, len(reqUserList)) 164 var i int 165 for userID := range reqUserList { 166 idSlice[i] = userID 167 i++ 168 } 169 170 // TODO: What if a user is deleted via the Control Panel? 171 //fmt.Printf("idSlice %+v\n", idSlice) 172 userList, err := c.Users.BulkGetMap(idSlice) 173 if err != nil { 174 return nil // TODO: Implement this! 175 } 176 177 // TODO: De-dupe this logic in common/topic_list.go? 178 //var sb strings.Builder 179 fps := make(map[int]c.QuickTools) 180 for _, t := range topicList { 181 //c.BuildTopicURLSb(&sb, c.NameToSlug(t.Title), t.ID) 182 //t.Link = sb.String() 183 //sb.Reset() 184 t.Link = c.BuildTopicURL(c.NameToSlug(t.Title), t.ID) 185 // TODO: Pass forum to something like t.Forum and use that instead of these two properties? Could be more flexible. 186 f := c.Forums.DirtyGet(t.ParentID) 187 t.ForumName = f.Name 188 t.ForumLink = f.Link 189 190 _, ok := fps[f.ID] 191 if !ok { 192 // TODO: Abstract this? 193 fp, err := c.FPStore.Get(f.ID, user.Group) 194 if err == c.ErrNoRows { 195 fp = c.BlankForumPerms() 196 } else if err != nil { 197 return c.InternalError(err, w, r) 198 } 199 if fp.Overrides && !user.IsSuperAdmin { 200 ccanDelete = fp.DeleteTopic 201 ccanLock = fp.CloseTopic 202 ccanMove = fp.MoveTopic 203 } else { 204 ccanDelete = user.Perms.DeleteTopic 205 ccanLock = user.Perms.CloseTopic 206 ccanMove = user.Perms.MoveTopic 207 } 208 if ccanDelete { 209 canDelete = true 210 } 211 if ccanLock { 212 canLock = true 213 } 214 if ccanMove { 215 canMove = true 216 } 217 fps[f.ID] = c.QuickTools{ccanDelete, ccanLock, ccanMove} 218 } 219 220 // TODO: Create a specialised function with a bit less overhead for getting the last page for a post count 221 _, _, lastPage := c.PageOffset(t.PostCount, 1, c.Config.ItemsPerPage) 222 t.LastPage = lastPage 223 // TODO: Avoid map if either is equal to the current user 224 t.Creator = userList[t.CreatedBy] 225 t.LastUser = userList[t.LastReplyBy] 226 } 227 228 // TODO: Reduce the amount of boilerplate here 229 if r.FormValue("js") == "1" { 230 outBytes, err := wsTopicList2(topicList, user, fps, pagi.LastPage).MarshalJSON() 231 if err != nil { 232 return c.InternalError(err, w, r) 233 } 234 w.Write(outBytes) 235 return nil 236 } 237 238 topicList2 := make([]c.TopicsRowMut, len(topicList)) 239 for i, t := range topicList { 240 var canMod bool 241 if fps == nil { 242 canMod = true 243 } else { 244 quickTools := fps[t.ParentID] 245 canMod = t.CreatedBy == user.ID || quickTools.CanDelete || quickTools.CanLock || quickTools.CanMove 246 } 247 topicList2[i] = c.TopicsRowMut{t, canMod} 248 } 249 250 h.Title = phrases.GetTitlePhrase("topics_search") 251 //log.Printf("cfids: %+v\n", cfids) 252 pi := c.TopicListPage{h, topicList2, forumList, c.Config.DefaultForum, c.TopicListSort{torder, false}, cfids, c.QuickTools{canDelete, canLock, canMove}, pagi} 253 return renderTemplate("topics", w, r, h, pi) 254 } 255 256 // TODO: Pass a struct back rather than passing back so many variables 257 //log.Printf("before forumList: %+v\n", forumList) 258 var fps map[int]c.QuickTools 259 if user.IsSuperAdmin { 260 //log.Print("user.IsSuperAdmin") 261 topicList, forumList, pagi, err = c.TopicList.GetList(page, tsorder, fids) 262 canLock, canMove = true, true 263 } else { 264 //log.Print("!user.IsSuperAdmin") 265 topicList, forumList, pagi, err = c.TopicList.GetListByGroup(group, page, tsorder, fids) 266 fps = make(map[int]c.QuickTools) 267 for _, f := range forumList { 268 fp, err := c.FPStore.Get(f.ID, user.Group) 269 if err == c.ErrNoRows { 270 fp = c.BlankForumPerms() 271 } else if err != nil { 272 return c.InternalError(err, w, r) 273 } 274 if fp.Overrides { 275 ccanDelete = fp.DeleteTopic 276 ccanLock = fp.CloseTopic 277 ccanMove = fp.MoveTopic 278 } else { 279 ccanDelete = user.Perms.DeleteTopic 280 ccanLock = user.Perms.CloseTopic 281 ccanMove = user.Perms.MoveTopic 282 } 283 if ccanDelete { 284 canDelete = true 285 } 286 if ccanLock { 287 canLock = true 288 } 289 if ccanMove { 290 canMove = true 291 } 292 fps[f.ID] = c.QuickTools{ccanDelete, ccanLock, ccanMove} 293 } 294 } 295 if err != nil { 296 return c.InternalError(err, w, r) 297 } 298 //log.Printf("after forumList: %+v\n", forumList) 299 //log.Printf("after topicList: %+v\n", topicList) 300 301 // TODO: Reduce the amount of boilerplate here 302 if r.FormValue("js") == "1" { 303 outBytes, err := wsTopicList2(topicList, user, fps, pagi.LastPage).MarshalJSON() 304 if err != nil { 305 return c.InternalError(err, w, r) 306 } 307 w.Write(outBytes) 308 return nil 309 } 310 311 topicList2 := make([]c.TopicsRowMut, len(topicList)) 312 for i, t := range topicList { 313 var canMod bool 314 if fps == nil { 315 canMod = true 316 } else { 317 quickTools := fps[t.ParentID] 318 canMod = t.CreatedBy == user.ID || quickTools.CanDelete || quickTools.CanLock || quickTools.CanMove 319 } 320 topicList2[i] = c.TopicsRowMut{t, canMod} 321 } 322 323 pi := c.TopicListPage{h, topicList2, forumList, c.Config.DefaultForum, c.TopicListSort{torder, false}, fids, c.QuickTools{canDelete, canLock, canMove}, pagi} 324 if r.FormValue("i") == "1" { 325 return renderTemplate("topics_mini", w, r, h, pi) 326 } 327 return renderTemplate("topics", w, r, h, pi) 328 }