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

     1  package routes
     2  
     3  import (
     4  	"database/sql"
     5  	"errors"
     6  	"html"
     7  	"math/rand"
     8  	"net/http"
     9  	"strconv"
    10  	"strings"
    11  
    12  	//"log"
    13  
    14  	c "github.com/Azareal/Gosora/common"
    15  	p "github.com/Azareal/Gosora/common/phrases"
    16  )
    17  
    18  func convoNotice(h *c.Header) {
    19  	//h.AddNotice("convo_dev")
    20  	c := rand.Intn(3)
    21  	h.AddNotice("convo_rand_" + strconv.Itoa(c))
    22  }
    23  
    24  func Convos(w http.ResponseWriter, r *http.Request, user *c.User, h *c.Header) c.RouteError {
    25  	accountEditHead("convos", w, r, user, h)
    26  	h.AddScript("convo.js")
    27  	h.AddSheet(h.Theme.Name + "/convo.css")
    28  	convoNotice(h)
    29  	ccount := c.Convos.GetUserCount(user.ID)
    30  	page, _ := strconv.Atoi(r.FormValue("page"))
    31  	offset, page, lastPage := c.PageOffset(ccount, page, c.Config.ItemsPerPage)
    32  	pageList := c.Paginate(page, lastPage, 5)
    33  
    34  	convos, err := c.Convos.GetUserExtra(user.ID, offset)
    35  	//log.Printf("convos: %+v\n", convos)
    36  	if err != sql.ErrNoRows && err != nil {
    37  		return c.InternalError(err, w, r)
    38  	}
    39  
    40  	var cRows []c.ConvoListRow
    41  	for _, convo := range convos {
    42  		var parti []*c.User
    43  		notMe := false
    44  		for _, u := range convo.Users {
    45  			if u.ID == user.ID {
    46  				continue
    47  			}
    48  			parti = append(parti, u)
    49  			notMe = true
    50  		}
    51  		if !notMe {
    52  			parti = convo.Users
    53  		}
    54  		cRows = append(cRows, c.ConvoListRow{convo, parti, len(parti) == 1})
    55  	}
    56  
    57  	pi := c.Account{h, "dashboard", "convos", c.ConvoListPage{h, cRows, c.Paginator{pageList, page, lastPage}}}
    58  	return renderTemplate("account", w, r, h, pi)
    59  }
    60  
    61  func Convo(w http.ResponseWriter, r *http.Request, user *c.User, h *c.Header, scid string) c.RouteError {
    62  	accountEditHead("convo", w, r, user, h)
    63  	h.AddSheet(h.Theme.Name + "/convo.css")
    64  	convoNotice(h)
    65  	cid, err := strconv.Atoi(scid)
    66  	if err != nil {
    67  		return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, user)
    68  	}
    69  	convo, err := c.Convos.Get(cid)
    70  	if err == sql.ErrNoRows {
    71  		return c.NotFound(w, r, h)
    72  	} else if err != nil {
    73  		return c.InternalError(err, w, r)
    74  	}
    75  	pcount := convo.PostsCount()
    76  	if pcount == 0 {
    77  		return c.NotFound(w, r, h)
    78  	}
    79  
    80  	page, _ := strconv.Atoi(r.FormValue("page"))
    81  	offset, page, lastPage := c.PageOffset(pcount, page, c.Config.ItemsPerPage)
    82  	pageList := c.Paginate(page, lastPage, 5)
    83  
    84  	posts, err := convo.Posts(offset, c.Config.ItemsPerPage)
    85  	// TODO: Report a better error for no posts
    86  	if err == sql.ErrNoRows {
    87  		return c.NotFound(w, r, h)
    88  	} else if err != nil {
    89  		return c.InternalError(err, w, r)
    90  	}
    91  
    92  	uids, err := convo.Uids()
    93  	if err == sql.ErrNoRows {
    94  		return c.NotFound(w, r, h)
    95  	} else if err != nil {
    96  		return c.InternalError(err, w, r)
    97  	}
    98  	umap, err := c.Users.BulkGetMap(uids)
    99  	if err == sql.ErrNoRows {
   100  		return c.NotFound(w, r, h)
   101  	} else if err != nil {
   102  		return c.InternalError(err, w, r)
   103  	}
   104  	users := make([]*c.User, len(umap))
   105  	i := 0
   106  	for _, user := range umap {
   107  		users[i] = user
   108  		i++
   109  	}
   110  
   111  	pitems := make([]c.ConvoViewRow, len(posts))
   112  	for i, post := range posts {
   113  		uuser, ok := umap[post.CreatedBy]
   114  		if !ok {
   115  			return c.InternalError(errors.New("convo post creator not in umap"), w, r)
   116  		}
   117  		canModify := user.ID == post.CreatedBy || user.IsSuperMod
   118  		pitems[i] = c.ConvoViewRow{post, uuser, "", 4, canModify}
   119  	}
   120  
   121  	canReply := user.Perms.UseConvos || user.Perms.UseConvosOnlyWithMod
   122  	if !user.Perms.UseConvos && user.Perms.UseConvosOnlyWithMod {
   123  		u, err := c.Users.Get(convo.CreatedBy)
   124  		if err != nil {
   125  			return c.InternalError(err, w, r)
   126  		}
   127  		if !u.IsSuperMod {
   128  			canReply = false
   129  		}
   130  	}
   131  
   132  	pi := c.Account{h, "dashboard", "convo", c.ConvoViewPage{h, convo, pitems, users, canReply, c.Paginator{pageList, page, lastPage}}}
   133  	return renderTemplate("account", w, r, h, pi)
   134  }
   135  
   136  func ConvosCreate(w http.ResponseWriter, r *http.Request, user *c.User, h *c.Header) c.RouteError {
   137  	accountEditHead("create_convo", w, r, user, h)
   138  	if !user.Perms.UseConvos && !user.Perms.UseConvosOnlyWithMod {
   139  		return c.NoPermissions(w, r, user)
   140  	}
   141  	convoNotice(h)
   142  	uid, err := strconv.Atoi(r.FormValue("with"))
   143  	if err != nil {
   144  		return c.LocalError("invalid integer in parameter with", w, r, user)
   145  	}
   146  	recp, err := c.Users.Get(uid)
   147  	if err != nil {
   148  		return c.LocalError("Unable to fetch user", w, r, user)
   149  	}
   150  	// TODO: Avoid potential double escape?
   151  	pi := c.Account{h, "dashboard", "create_convo", c.ConvoCreatePage{h, html.EscapeString(recp.Name)}}
   152  	return renderTemplate("account", w, r, h, pi)
   153  }
   154  
   155  func ConvosCreateSubmit(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
   156  	_, ferr := c.SimpleUserCheck(w, r, user)
   157  	if ferr != nil {
   158  		return ferr
   159  	}
   160  	if !user.Perms.UseConvos && !user.Perms.UseConvosOnlyWithMod {
   161  		return c.NoPermissions(w, r, user)
   162  	}
   163  
   164  	sRecps := c.SanitiseSingleLine(r.PostFormValue("recp"))
   165  	body := c.PreparseMessage(r.PostFormValue("body"))
   166  	rlist := []int{}
   167  
   168  	// De-dupe recipients
   169  	var recps []string
   170  	unames := make(map[string]struct{})
   171  	for _, recp := range strings.Split(sRecps, ",") {
   172  		recp = strings.TrimSpace(recp)
   173  		_, exists := unames[recp]
   174  		if !exists {
   175  			recps = append(recps, recp)
   176  			unames[recp] = struct{}{}
   177  		}
   178  	}
   179  
   180  	max := 10 // max number of recipients that can be added at once
   181  	for i, recp := range recps {
   182  		if i >= max {
   183  			break
   184  		}
   185  
   186  		u, err := c.Users.GetByName(recp)
   187  		if err == sql.ErrNoRows {
   188  			return c.LocalError("One of the recipients doesn't exist", w, r, user)
   189  		} else if err != nil {
   190  			return c.InternalError(err, w, r)
   191  		}
   192  		// TODO: Should we kick them out of existing conversations if they're moved into a group without permission or the permission is revoked from their group? We might want to give them a chance to delete their messages though to avoid privacy headaches here and it may only be temporarily to tackle a specific incident.
   193  		if !u.Perms.UseConvos && !u.Perms.UseConvosOnlyWithMod {
   194  			return c.LocalError("One of the recipients doesn't have permission to use the conversations system", w, r, user)
   195  		}
   196  		if !user.Perms.UseConvos && !u.IsSuperMod && user.Perms.UseConvosOnlyWithMod {
   197  			return c.LocalError("You are only allowed to message global moderators.", w, r, user)
   198  		}
   199  		if !user.IsSuperMod && !u.Perms.UseConvos && u.Perms.UseConvosOnlyWithMod {
   200  			return c.LocalError("One of the recipients doesn't have permission to engage with conversation with you.", w, r, user)
   201  		}
   202  		blocked, err := c.UserBlocks.IsBlockedBy(u.ID, user.ID)
   203  		if err != nil {
   204  			return c.InternalError(err, w, r)
   205  		}
   206  		// Supermods can bypass blocks so they can tell people off when they do something stupid or have to convey important information
   207  		if blocked && !user.IsSuperMod {
   208  			return c.LocalError("You don't have permission to send messages to one of these users.", w, r, user)
   209  		}
   210  
   211  		rlist = append(rlist, u.ID)
   212  	}
   213  
   214  	cid, err := c.Convos.Create(body, user.ID, rlist)
   215  	if err != nil {
   216  		return c.InternalError(err, w, r)
   217  	}
   218  
   219  	// TODO: Don't bother making the subscription if the convo creator is the only recipient?
   220  	for _, uid := range rlist {
   221  		if uid == user.ID {
   222  			continue
   223  		}
   224  		err := c.Subscriptions.Add(uid, cid, "convo")
   225  		if err != nil {
   226  			return c.InternalError(err, w, r)
   227  		}
   228  	}
   229  	err = c.Subscriptions.Add(user.ID, cid, "convo")
   230  	if err != nil {
   231  		return c.InternalError(err, w, r)
   232  	}
   233  
   234  	err = c.AddActivityAndNotifyAll(c.Alert{ActorID: user.ID, Event: "create", ElementType: "convo", ElementID: cid, Actor: user})
   235  	if err != nil {
   236  		return c.InternalError(err, w, r)
   237  	}
   238  
   239  	http.Redirect(w, r, "/user/convo/"+strconv.Itoa(cid), http.StatusSeeOther)
   240  	return nil
   241  }
   242  
   243  /*func ConvosDeleteSubmit(w http.ResponseWriter, r *http.Request, user *c.User, scid string) c.RouteError {
   244  	_, ferr := c.SimpleUserCheck(w, r, user)
   245  	if ferr != nil {
   246  		return ferr
   247  	}
   248  	cid, err := strconv.Atoi(scid)
   249  	if err != nil {
   250  		return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, user)
   251  	}
   252  	if err := c.Convos.Delete(cid); err != nil {
   253  		return c.InternalError(err, w, r)
   254  	}
   255  	http.Redirect(w, r, "/user/convos/", http.StatusSeeOther)
   256  	return nil
   257  }*/
   258  
   259  func ConvosCreateReplySubmit(w http.ResponseWriter, r *http.Request, user *c.User, scid string) c.RouteError {
   260  	_, ferr := c.SimpleUserCheck(w, r, user)
   261  	if ferr != nil {
   262  		return ferr
   263  	}
   264  	if !user.Perms.UseConvos && !user.Perms.UseConvosOnlyWithMod {
   265  		return c.NoPermissions(w, r, user)
   266  	}
   267  	cid, err := strconv.Atoi(scid)
   268  	if err != nil {
   269  		return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, user)
   270  	}
   271  
   272  	convo, err := c.Convos.Get(cid)
   273  	if err == sql.ErrNoRows {
   274  		return c.NotFound(w, r, nil)
   275  	} else if err != nil {
   276  		return c.InternalError(err, w, r)
   277  	}
   278  	pcount := convo.PostsCount()
   279  	if pcount == 0 {
   280  		return c.NotFound(w, r, nil)
   281  	}
   282  	if !convo.Has(user.ID) {
   283  		return c.LocalError("You are not in this conversation.", w, r, user)
   284  	}
   285  	// TODO: Let the user reply if they're the convo creator in a convo with a mod
   286  	if !user.Perms.UseConvos && user.Perms.UseConvosOnlyWithMod {
   287  		u, err := c.Users.Get(convo.CreatedBy)
   288  		if err != nil {
   289  			return c.InternalError(err, w, r)
   290  		}
   291  		if !u.IsSuperMod {
   292  			return c.LocalError("You're only allowed to talk to global moderators.", w, r, user)
   293  		}
   294  	}
   295  
   296  	body := c.PreparseMessage(r.PostFormValue("content"))
   297  	post := &c.ConversationPost{CID: cid, Body: body, CreatedBy: user.ID}
   298  	pid, err := post.Create()
   299  	if err != nil {
   300  		return c.InternalError(err, w, r)
   301  	}
   302  	err = c.AddActivityAndNotifyAll(c.Alert{ActorID: user.ID, Event: "reply", ElementType: "convo", ElementID: cid, Actor: user, Extra: strconv.Itoa(pid)})
   303  	if err != nil {
   304  		return c.InternalError(err, w, r)
   305  	}
   306  
   307  	http.Redirect(w, r, "/user/convo/"+strconv.Itoa(convo.ID), http.StatusSeeOther)
   308  	return nil
   309  }
   310  
   311  func ConvosDeleteReplySubmit(w http.ResponseWriter, r *http.Request, u *c.User, scpid string) c.RouteError {
   312  	_, ferr := c.SimpleUserCheck(w, r, u)
   313  	if ferr != nil {
   314  		return ferr
   315  	}
   316  	cpid, err := strconv.Atoi(scpid)
   317  	if err != nil {
   318  		return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, u)
   319  	}
   320  
   321  	post := &c.ConversationPost{ID: cpid}
   322  	err = post.Fetch()
   323  	if err == sql.ErrNoRows {
   324  		return c.NotFound(w, r, nil)
   325  	} else if err != nil {
   326  		return c.InternalError(err, w, r)
   327  	}
   328  
   329  	convo, err := c.Convos.Get(post.CID)
   330  	if err == sql.ErrNoRows {
   331  		return c.NotFound(w, r, nil)
   332  	} else if err != nil {
   333  		return c.InternalError(err, w, r)
   334  	}
   335  	pcount := convo.PostsCount()
   336  	if pcount == 0 {
   337  		return c.NotFound(w, r, nil)
   338  	}
   339  	if u.ID != post.CreatedBy && !u.IsSuperMod {
   340  		return c.NoPermissions(w, r, u)
   341  	}
   342  
   343  	posts, err := convo.Posts(0, c.Config.ItemsPerPage)
   344  	// TODO: Report a better error for no posts
   345  	if err == sql.ErrNoRows {
   346  		return c.NotFound(w, r, nil)
   347  	} else if err != nil {
   348  		return c.InternalError(err, w, r)
   349  	}
   350  
   351  	if post.ID == posts[0].ID {
   352  		err = c.Convos.Delete(convo.ID)
   353  	} else {
   354  		err = post.Delete()
   355  	}
   356  	if err != nil {
   357  		return c.InternalError(err, w, r)
   358  	}
   359  
   360  	http.Redirect(w, r, "/user/convo/"+strconv.Itoa(post.CID), http.StatusSeeOther)
   361  	return nil
   362  }
   363  
   364  func ConvosEditReplySubmit(w http.ResponseWriter, r *http.Request, user *c.User, scpid string) c.RouteError {
   365  	_, ferr := c.SimpleUserCheck(w, r, user)
   366  	if ferr != nil {
   367  		return ferr
   368  	}
   369  	cpid, err := strconv.Atoi(scpid)
   370  	if err != nil {
   371  		return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, user)
   372  	}
   373  	if !user.Perms.UseConvos {
   374  		return c.NoPermissions(w, r, user)
   375  	}
   376  	js := r.PostFormValue("js") == "1"
   377  
   378  	post := &c.ConversationPost{ID: cpid}
   379  	err = post.Fetch()
   380  	if err == sql.ErrNoRows {
   381  		return c.NotFound(w, r, nil)
   382  	} else if err != nil {
   383  		return c.InternalError(err, w, r)
   384  	}
   385  
   386  	convo, err := c.Convos.Get(post.CID)
   387  	if err == sql.ErrNoRows {
   388  		return c.NotFound(w, r, nil)
   389  	} else if err != nil {
   390  		return c.InternalError(err, w, r)
   391  	}
   392  	pcount := convo.PostsCount()
   393  	if pcount == 0 {
   394  		return c.NotFound(w, r, nil)
   395  	}
   396  	if user.ID != post.CreatedBy && !user.IsSuperMod {
   397  		return c.NoPermissions(w, r, user)
   398  	}
   399  	if !convo.Has(user.ID) {
   400  		return c.LocalError("You are not in this conversation.", w, r, user)
   401  	}
   402  
   403  	post.Body = c.PreparseMessage(r.PostFormValue("edit_item"))
   404  	err = post.Update()
   405  	if err != nil {
   406  		return c.InternalError(err, w, r)
   407  	}
   408  	return actionSuccess(w, r, "/user/convo/"+strconv.Itoa(post.CID), js)
   409  }
   410  
   411  func RelationsBlockCreate(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header, spid string) c.RouteError {
   412  	h.Title = p.GetTitlePhrase("create_block")
   413  	pid, err := strconv.Atoi(spid)
   414  	if err != nil {
   415  		return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, u)
   416  	}
   417  	puser, err := c.Users.Get(pid)
   418  	if err == sql.ErrNoRows {
   419  		return c.LocalError("The user you're trying to block doesn't exist.", w, r, u)
   420  	} else if err != nil {
   421  		return c.InternalError(err, w, r)
   422  	}
   423  
   424  	pi := c.Page{h, nil, c.AreYouSure{"/user/block/create/submit/" + strconv.Itoa(puser.ID), p.GetTmplPhrase("create_block_msg")}}
   425  	return renderTemplate("are_you_sure", w, r, h, pi)
   426  }
   427  
   428  func RelationsBlockCreateSubmit(w http.ResponseWriter, r *http.Request, u *c.User, spid string) c.RouteError {
   429  	pid, err := strconv.Atoi(spid)
   430  	if err != nil {
   431  		return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, u)
   432  	}
   433  	puser, err := c.Users.Get(pid)
   434  	if err == sql.ErrNoRows {
   435  		return c.LocalError("The user you're trying to block doesn't exist.", w, r, u)
   436  	} else if err != nil {
   437  		return c.InternalError(err, w, r)
   438  	}
   439  	if u.ID == puser.ID {
   440  		return c.LocalError("You can't block yourself.", w, r, u)
   441  	}
   442  
   443  	err = c.UserBlocks.Add(u.ID, puser.ID)
   444  	if err != nil {
   445  		return c.InternalError(err, w, r)
   446  	}
   447  
   448  	http.Redirect(w, r, "/user/"+strconv.Itoa(puser.ID), http.StatusSeeOther)
   449  	return nil
   450  }
   451  
   452  func RelationsBlockRemove(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header, spid string) c.RouteError {
   453  	h.Title = p.GetTitlePhrase("remove_block")
   454  	pid, err := strconv.Atoi(spid)
   455  	if err != nil {
   456  		return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, u)
   457  	}
   458  	puser, err := c.Users.Get(pid)
   459  	if err == sql.ErrNoRows {
   460  		return c.LocalError("The user you're trying to block doesn't exist.", w, r, u)
   461  	} else if err != nil {
   462  		return c.InternalError(err, w, r)
   463  	}
   464  
   465  	pi := c.Page{h, nil, c.AreYouSure{"/user/block/remove/submit/" + strconv.Itoa(puser.ID), p.GetTmplPhrase("remove_block_msg")}}
   466  	return renderTemplate("are_you_sure", w, r, h, pi)
   467  }
   468  
   469  func RelationsBlockRemoveSubmit(w http.ResponseWriter, r *http.Request, u *c.User, spid string) c.RouteError {
   470  	pid, err := strconv.Atoi(spid)
   471  	if err != nil {
   472  		return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, u)
   473  	}
   474  	puser, err := c.Users.Get(pid)
   475  	if err == sql.ErrNoRows {
   476  		return c.LocalError("The user you're trying to unblock doesn't exist.", w, r, u)
   477  	} else if err != nil {
   478  		return c.InternalError(err, w, r)
   479  	}
   480  
   481  	err = c.UserBlocks.Remove(u.ID, puser.ID)
   482  	if err != nil {
   483  		return c.InternalError(err, w, r)
   484  	}
   485  
   486  	http.Redirect(w, r, "/user/"+strconv.Itoa(puser.ID), http.StatusSeeOther)
   487  	return nil
   488  }