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  }