package routes import ( "database/sql" "log" "net/http" "strconv" "strings" c "git.tuxpa.in/a/gosora/common" "git.tuxpa.in/a/gosora/common/phrases" ) func wsTopicList(topicList []*c.TopicsRow, lastPage int) *c.WsTopicList { wsTopicList := make([]*c.WsTopicsRow, len(topicList)) for i, tr := range topicList { wsTopicList[i] = tr.WebSockets() } return &c.WsTopicList{wsTopicList, lastPage, 0} } func wsTopicList2(topicList []*c.TopicsRow, u *c.User, fps map[int]c.QuickTools, lastPage int) *c.WsTopicList { wsTopicList := make([]*c.WsTopicsRow, len(topicList)) for i, t := range topicList { var canMod bool if fps == nil { canMod = true } else { quickTools := fps[t.ParentID] canMod = t.CreatedBy == u.ID || quickTools.CanDelete || quickTools.CanLock || quickTools.CanMove } wsTopicList[i] = t.WebSockets2(canMod) } return &c.WsTopicList{wsTopicList, lastPage, 0} } func TopicList(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header) c.RouteError { /*skip, rerr := h.Hooks.VhookSkippable("route_topic_list_start", w, r, u, h) if skip || rerr != nil { return rerr }*/ skip, rerr := c.H_route_topic_list_start_hook(h.Hooks, w, r, u, h) if skip || rerr != nil { return rerr } return TopicListCommon(w, r, u, h, "lastupdated", 0) } func TopicListMostViewed(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header) c.RouteError { skip, rerr := h.Hooks.VhookSkippable("route_topic_list_mostviewed_start", w, r, u, h) if skip || rerr != nil { return rerr } return TopicListCommon(w, r, u, h, "mostviewed", c.TopicListMostViewed) } func TopicListWeekViews(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header) c.RouteError { skip, rerr := h.Hooks.VhookSkippable("route_topic_list_weekviews_start", w, r, u, h) if skip || rerr != nil { return rerr } return TopicListCommon(w, r, u, h, "weekviews", c.TopicListWeekViews) } // TODO: Implement search func TopicListCommon(w http.ResponseWriter, r *http.Request, user *c.User, h *c.Header, torder string, tsorder int) c.RouteError { h.Title = phrases.GetTitlePhrase("topics") h.Zone = "topics" h.Path = "/topics/" h.MetaDesc = h.Settings["meta_desc"].(string) group, err := c.Groups.Get(user.Group) if err != nil { log.Printf("Group #%d doesn't exist despite being used by c.User #%d", user.Group, user.ID) return c.LocalError("Something weird happened", w, r, user) } var forumList []c.Forum // Get the current page page, _ := strconv.Atoi(r.FormValue("page")) sfids := r.FormValue("fids") var fids []int if sfids != "" { for _, sfid := range strings.Split(sfids, ",") { fid, err := strconv.Atoi(sfid) if err != nil { return c.LocalError("Invalid fid", w, r, user) } fids = append(fids, fid) } if len(fids) == 1 { f, err := c.Forums.Get(fids[0]) if err != nil { return c.LocalError("Invalid fid forum", w, r, user) } h.Title = f.Name h.ZoneID = f.ID forumList = append(forumList, *f) } } // TODO: Allow multiple forums in searches // TODO: Simplify this block after initially landing search var topicList []*c.TopicsRow var pagi c.Paginator var canDelete, ccanDelete, canLock, ccanLock, canMove, ccanMove bool q := r.FormValue("q") if q != "" && c.RepliesSearch != nil { var canSee []int if user.IsSuperAdmin { canSee, err = c.Forums.GetAllVisibleIDs() if err != nil { return c.InternalError(err, w, r) } } else { canSee = group.CanSee } var cfids []int if len(fids) > 0 { inSlice := func(haystack []int, needle int) bool { for _, it := range haystack { if needle == it { return true } } return false } for _, fid := range fids { if inSlice(canSee, fid) { f := c.Forums.DirtyGet(fid) if f.Name != "" && f.Active && (f.ParentType == "" || f.ParentType == "forum") && f.TopicCount != 0 { // TODO: Add a hook here for plugin_guilds? cfids = append(cfids, fid) } } } } else { cfids = canSee } tids, err := c.RepliesSearch.Query(q, cfids) if err != nil && err != sql.ErrNoRows { return c.InternalError(err, w, r) } //log.Printf("tids %+v\n", tids) // TODO: Handle the case where there aren't any items... // TODO: Add a BulkGet method which returns a slice? tMap, err := c.Topics.BulkGetMap(tids) if err != nil { return c.InternalError(err, w, r) } // TODO: Cache emptied map across requests with sync pool reqUserList := make(map[int]bool) for _, t := range tMap { reqUserList[t.CreatedBy] = true reqUserList[t.LastReplyBy] = true topicList = append(topicList, t.TopicsRow()) } //fmt.Printf("reqUserList %+v\n", reqUserList) // Convert the user ID map to a slice, then bulk load the users idSlice := make([]int, len(reqUserList)) var i int for userID := range reqUserList { idSlice[i] = userID i++ } // TODO: What if a user is deleted via the Control Panel? //fmt.Printf("idSlice %+v\n", idSlice) userList, err := c.Users.BulkGetMap(idSlice) if err != nil { return nil // TODO: Implement this! } // TODO: De-dupe this logic in common/topic_list.go? //var sb strings.Builder fps := make(map[int]c.QuickTools) for _, t := range topicList { //c.BuildTopicURLSb(&sb, c.NameToSlug(t.Title), t.ID) //t.Link = sb.String() //sb.Reset() t.Link = c.BuildTopicURL(c.NameToSlug(t.Title), t.ID) // TODO: Pass forum to something like t.Forum and use that instead of these two properties? Could be more flexible. f := c.Forums.DirtyGet(t.ParentID) t.ForumName = f.Name t.ForumLink = f.Link _, ok := fps[f.ID] if !ok { // TODO: Abstract this? fp, err := c.FPStore.Get(f.ID, user.Group) if err == c.ErrNoRows { fp = c.BlankForumPerms() } else if err != nil { return c.InternalError(err, w, r) } if fp.Overrides && !user.IsSuperAdmin { ccanDelete = fp.DeleteTopic ccanLock = fp.CloseTopic ccanMove = fp.MoveTopic } else { ccanDelete = user.Perms.DeleteTopic ccanLock = user.Perms.CloseTopic ccanMove = user.Perms.MoveTopic } if ccanDelete { canDelete = true } if ccanLock { canLock = true } if ccanMove { canMove = true } fps[f.ID] = c.QuickTools{ccanDelete, ccanLock, ccanMove} } // TODO: Create a specialised function with a bit less overhead for getting the last page for a post count _, _, lastPage := c.PageOffset(t.PostCount, 1, c.Config.ItemsPerPage) t.LastPage = lastPage // TODO: Avoid map if either is equal to the current user t.Creator = userList[t.CreatedBy] t.LastUser = userList[t.LastReplyBy] } // TODO: Reduce the amount of boilerplate here if r.FormValue("js") == "1" { outBytes, err := wsTopicList2(topicList, user, fps, pagi.LastPage).MarshalJSON() if err != nil { return c.InternalError(err, w, r) } w.Write(outBytes) return nil } topicList2 := make([]c.TopicsRowMut, len(topicList)) for i, t := range topicList { var canMod bool if fps == nil { canMod = true } else { quickTools := fps[t.ParentID] canMod = t.CreatedBy == user.ID || quickTools.CanDelete || quickTools.CanLock || quickTools.CanMove } topicList2[i] = c.TopicsRowMut{t, canMod} } h.Title = phrases.GetTitlePhrase("topics_search") //log.Printf("cfids: %+v\n", cfids) pi := c.TopicListPage{h, topicList2, forumList, c.Config.DefaultForum, c.TopicListSort{torder, false}, cfids, c.QuickTools{canDelete, canLock, canMove}, pagi} return renderTemplate("topics", w, r, h, pi) } // TODO: Pass a struct back rather than passing back so many variables //log.Printf("before forumList: %+v\n", forumList) var fps map[int]c.QuickTools if user.IsSuperAdmin { //log.Print("user.IsSuperAdmin") topicList, forumList, pagi, err = c.TopicList.GetList(page, tsorder, fids) canLock, canMove = true, true } else { //log.Print("!user.IsSuperAdmin") topicList, forumList, pagi, err = c.TopicList.GetListByGroup(group, page, tsorder, fids) fps = make(map[int]c.QuickTools) for _, f := range forumList { fp, err := c.FPStore.Get(f.ID, user.Group) if err == c.ErrNoRows { fp = c.BlankForumPerms() } else if err != nil { return c.InternalError(err, w, r) } if fp.Overrides { ccanDelete = fp.DeleteTopic ccanLock = fp.CloseTopic ccanMove = fp.MoveTopic } else { ccanDelete = user.Perms.DeleteTopic ccanLock = user.Perms.CloseTopic ccanMove = user.Perms.MoveTopic } if ccanDelete { canDelete = true } if ccanLock { canLock = true } if ccanMove { canMove = true } fps[f.ID] = c.QuickTools{ccanDelete, ccanLock, ccanMove} } } if err != nil { return c.InternalError(err, w, r) } //log.Printf("after forumList: %+v\n", forumList) //log.Printf("after topicList: %+v\n", topicList) // TODO: Reduce the amount of boilerplate here if r.FormValue("js") == "1" { outBytes, err := wsTopicList2(topicList, user, fps, pagi.LastPage).MarshalJSON() if err != nil { return c.InternalError(err, w, r) } w.Write(outBytes) return nil } topicList2 := make([]c.TopicsRowMut, len(topicList)) for i, t := range topicList { var canMod bool if fps == nil { canMod = true } else { quickTools := fps[t.ParentID] canMod = t.CreatedBy == user.ID || quickTools.CanDelete || quickTools.CanLock || quickTools.CanMove } topicList2[i] = c.TopicsRowMut{t, canMod} } pi := c.TopicListPage{h, topicList2, forumList, c.Config.DefaultForum, c.TopicListSort{torder, false}, fids, c.QuickTools{canDelete, canLock, canMove}, pagi} if r.FormValue("i") == "1" { return renderTemplate("topics_mini", w, r, h, pi) } return renderTemplate("topics", w, r, h, pi) }