mirror of
https://github.com/cwinfo/matterbridge.git
synced 2025-06-27 16:59:23 +00:00
Sync with mattermost 3.6.0
This commit is contained in:
279
vendor/github.com/mattermost/platform/model/client.go
generated
vendored
279
vendor/github.com/mattermost/platform/model/client.go
generated
vendored
@ -6,7 +6,6 @@ package model
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
@ -15,6 +14,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -48,6 +49,13 @@ type Result struct {
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
type ResponseMetadata struct {
|
||||
StatusCode int
|
||||
Error *AppError
|
||||
RequestId string
|
||||
Etag string
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
Url string // The location of the server like "http://localhost:8065"
|
||||
ApiUrl string // The api location of the server like "http://localhost:8065/api/v3"
|
||||
@ -291,34 +299,6 @@ func (c *Client) GetPing() (map[string]string, *AppError) {
|
||||
|
||||
// Team Routes Section
|
||||
|
||||
// SignupTeam sends an email with a team sign-up link to the provided address if email
|
||||
// verification is enabled, otherwise it returns a map with a "follow_link" entry
|
||||
// containing the team sign-up link.
|
||||
func (c *Client) SignupTeam(email string, displayName string) (*Result, *AppError) {
|
||||
m := make(map[string]string)
|
||||
m["email"] = email
|
||||
m["display_name"] = displayName
|
||||
if r, err := c.DoApiPost("/teams/signup", MapToJson(m)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// CreateTeamFromSignup creates a team based on the provided TeamSignup struct. On success
|
||||
// it returns the TeamSignup struct.
|
||||
func (c *Client) CreateTeamFromSignup(teamSignup *TeamSignup) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost("/teams/create_from_signup", teamSignup.ToJson()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), TeamSignupFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// CreateTeam creates a team based on the provided Team struct. On success it returns
|
||||
// the Team struct with the Id, CreateAt and other server-decided fields populated.
|
||||
func (c *Client) CreateTeam(team *Team) (*Result, *AppError) {
|
||||
@ -500,6 +480,32 @@ func (c *Client) GetUser(id string, etag string) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
// getByUsername returns a user based on a provided username string. Must be authenticated.
|
||||
func (c *Client) GetByUsername(username string, etag string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(fmt.Sprintf("/users/name/%v", username), "", etag); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// getByEmail returns a user based on a provided username string. Must be authenticated.
|
||||
func (c *Client) GetByEmail(email string, etag string) (*User, *ResponseMetadata) {
|
||||
if r, err := c.DoApiGet(fmt.Sprintf("/users/email/%v", email), "", etag); err != nil {
|
||||
return nil, &ResponseMetadata{StatusCode: r.StatusCode, Error: err}
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return UserFromJson(r.Body),
|
||||
&ResponseMetadata{
|
||||
StatusCode: r.StatusCode,
|
||||
RequestId: r.Header.Get(HEADER_REQUEST_ID),
|
||||
Etag: r.Header.Get(HEADER_ETAG_SERVER),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetMe returns the current user.
|
||||
func (c *Client) GetMe(etag string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet("/users/me", "", etag); err != nil {
|
||||
@ -610,6 +616,19 @@ func (c *Client) AutocompleteUsersInTeam(term string) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
// AutocompleteUsers returns a list for autocompletion of users on the system that match the provided term,
|
||||
// matching against username, full name and nickname. Must be authenticated.
|
||||
func (c *Client) AutocompleteUsers(term string) (*Result, *AppError) {
|
||||
url := fmt.Sprintf("/users/autocomplete?term=%s", url.QueryEscape(term))
|
||||
if r, err := c.DoApiGet(url, "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), UserListFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// LoginById authenticates a user by user id and password.
|
||||
func (c *Client) LoginById(id string, password string) (*Result, *AppError) {
|
||||
m := make(map[string]string)
|
||||
@ -802,12 +821,9 @@ func (c *Client) EmailToLDAP(m map[string]string) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Command(channelId string, command string, suggest bool) (*Result, *AppError) {
|
||||
m := make(map[string]string)
|
||||
m["command"] = command
|
||||
m["channelId"] = channelId
|
||||
m["suggest"] = strconv.FormatBool(suggest)
|
||||
if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/execute", MapToJson(m)); err != nil {
|
||||
func (c *Client) Command(channelId string, command string) (*Result, *AppError) {
|
||||
args := &CommandArgs{ChannelId: channelId, Command: command}
|
||||
if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/execute", args.ToJson()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
@ -846,6 +862,16 @@ func (c *Client) CreateCommand(cmd *Command) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) UpdateCommand(cmd *Command) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/update", cmd.ToJson()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), CommandFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) RegenCommandToken(data map[string]string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/regen_token", MapToJson(data)); err != nil {
|
||||
return nil, err
|
||||
@ -940,6 +966,16 @@ func (c *Client) ReloadConfig() (bool, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) InvalidateAllCaches() (bool, *AppError) {
|
||||
c.clearExtraProperties()
|
||||
if r, err := c.DoApiGet("/admin/invalidate_all_caches", "", ""); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
c.fillInExtraProperties(r)
|
||||
return c.CheckStatusOK(r), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) SaveConfig(config *Config) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost("/admin/save_config", config.ToJson()); err != nil {
|
||||
return nil, err
|
||||
@ -1143,6 +1179,7 @@ func (c *Client) GetChannel(id, etag string) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
// SCHEDULED FOR DEPRECATION IN 3.7 - use GetMoreChannelsPage instead
|
||||
func (c *Client) GetMoreChannels(etag string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/more", "", etag); err != nil {
|
||||
return nil, err
|
||||
@ -1153,6 +1190,43 @@ func (c *Client) GetMoreChannels(etag string) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetMoreChannelsPage will return a page of open channels the user is not in based on
|
||||
// the provided offset and limit. Must be authenticated.
|
||||
func (c *Client) GetMoreChannelsPage(offset int, limit int) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(fmt.Sprintf(c.GetTeamRoute()+"/channels/more/%v/%v", offset, limit), "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// SearchMoreChannels will return a list of open channels the user is not in, that matches
|
||||
// the search criteria provided. Must be authenticated.
|
||||
func (c *Client) SearchMoreChannels(channelSearch ChannelSearch) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/more/search", channelSearch.ToJson()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// AutocompleteChannels will return a list of open channels that match the provided
|
||||
// string. Must be authenticated.
|
||||
func (c *Client) AutocompleteChannels(term string) (*Result, *AppError) {
|
||||
url := fmt.Sprintf("%s/channels/autocomplete?term=%s", c.GetTeamRoute(), url.QueryEscape(term))
|
||||
if r, err := c.DoApiGet(url, "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetChannelCounts(etag string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/counts", "", etag); err != nil {
|
||||
return nil, err
|
||||
@ -1173,6 +1247,16 @@ func (c *Client) GetChannels(etag string) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetChannelByName(channelName string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetChannelNameRoute(channelName), "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) JoinChannel(id string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost(c.GetChannelRoute(id)+"/join", ""); err != nil {
|
||||
return nil, err
|
||||
@ -1240,6 +1324,7 @@ func (c *Client) RemoveChannelMember(id, user_id string) (*Result, *AppError) {
|
||||
// UpdateLastViewedAt will mark a channel as read.
|
||||
// The channelId indicates the channel to mark as read. If active is true, push notifications
|
||||
// will be cleared if there are unread messages. The default for active is true.
|
||||
// SCHEDULED FOR DEPRECATION IN 3.8 - use ViewChannel instead
|
||||
func (c *Client) UpdateLastViewedAt(channelId string, active bool) (*Result, *AppError) {
|
||||
data := make(map[string]interface{})
|
||||
data["active"] = active
|
||||
@ -1252,6 +1337,23 @@ func (c *Client) UpdateLastViewedAt(channelId string, active bool) (*Result, *Ap
|
||||
}
|
||||
}
|
||||
|
||||
// ViewChannel performs all the actions related to viewing a channel. This includes marking
|
||||
// the channel and the previous one as read, and marking the channel as being actively viewed.
|
||||
// ChannelId is required but may be blank to indicate no channel is being viewed.
|
||||
// PrevChannelId is optional, populate to indicate a channel switch occurred.
|
||||
func (c *Client) ViewChannel(params ChannelView) (bool, *ResponseMetadata) {
|
||||
if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/view", params.ToJson()); err != nil {
|
||||
return false, &ResponseMetadata{StatusCode: r.StatusCode, Error: err}
|
||||
} else {
|
||||
return c.CheckStatusOK(r),
|
||||
&ResponseMetadata{
|
||||
StatusCode: r.StatusCode,
|
||||
RequestId: r.Header.Get(HEADER_REQUEST_ID),
|
||||
Etag: r.Header.Get(HEADER_ETAG_SERVER),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetChannelStats(id string, etag string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/stats", "", etag); err != nil {
|
||||
return nil, err
|
||||
@ -1272,6 +1374,18 @@ func (c *Client) GetChannelMember(channelId string, userId string) (*Result, *Ap
|
||||
}
|
||||
}
|
||||
|
||||
// GetChannelMembersByIds will return channel member objects as an array based on the
|
||||
// channel id and a list of user ids provided. Must be authenticated.
|
||||
func (c *Client) GetChannelMembersByIds(channelId string, userIds []string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/members/ids", ArrayToJson(userIds)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), ChannelMembersFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) CreatePost(post *Post) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost(c.GetChannelRoute(post.ChannelId)+"/posts/create", post.ToJson()); err != nil {
|
||||
return nil, err
|
||||
@ -1342,6 +1456,21 @@ func (c *Client) GetPost(channelId string, postId string, etag string) (*Result,
|
||||
}
|
||||
}
|
||||
|
||||
// GetPostById returns a post and any posts in the same thread by post id
|
||||
func (c *Client) GetPostById(postId string, etag string) (*PostList, *ResponseMetadata) {
|
||||
if r, err := c.DoApiGet(c.GetTeamRoute()+fmt.Sprintf("/posts/%v", postId), "", etag); err != nil {
|
||||
return nil, &ResponseMetadata{StatusCode: r.StatusCode, Error: err}
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return PostListFromJson(r.Body),
|
||||
&ResponseMetadata{
|
||||
StatusCode: r.StatusCode,
|
||||
RequestId: r.Header.Get(HEADER_REQUEST_ID),
|
||||
Etag: r.Header.Get(HEADER_ETAG_SERVER),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) DeletePost(channelId string, postId string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/delete", postId), ""); err != nil {
|
||||
return nil, err
|
||||
@ -1629,6 +1758,7 @@ func (c *Client) GetStatusesByIds(userIds []string) (*Result, *AppError) {
|
||||
// SetActiveChannel sets the the channel id the user is currently viewing.
|
||||
// The channelId key is required but the value can be blank. Returns standard
|
||||
// response.
|
||||
// SCHEDULED FOR DEPRECATION IN 3.8 - use ViewChannel instead
|
||||
func (c *Client) SetActiveChannel(channelId string) (*Result, *AppError) {
|
||||
data := map[string]string{}
|
||||
data["channel_id"] = channelId
|
||||
@ -1663,6 +1793,36 @@ func (c *Client) GetTeamMembers(teamId string, offset int, limit int) (*Result,
|
||||
}
|
||||
}
|
||||
|
||||
// GetMyTeamMembers will return an array with team member objects that the current user
|
||||
// is a member of. Must be authenticated.
|
||||
func (c *Client) GetMyTeamMembers() (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet("/teams/members", "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), TeamMembersFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetMyTeamsUnread will return an array with TeamUnread objects that contain the amount of
|
||||
// unread messages and mentions the current user has for the teams it belongs to.
|
||||
// An optional team ID can be set to exclude that team from the results. Must be authenticated.
|
||||
func (c *Client) GetMyTeamsUnread(teamId string) (*Result, *AppError) {
|
||||
endpoint := "/teams/unread"
|
||||
|
||||
if teamId != "" {
|
||||
endpoint += fmt.Sprintf("?id=%s", url.QueryEscape(teamId))
|
||||
}
|
||||
if r, err := c.DoApiGet(endpoint, "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), TeamsUnreadFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetTeamMember will return a team member object based on the team id and user id provided.
|
||||
// Must be authenticated.
|
||||
func (c *Client) GetTeamMember(teamId string, userId string) (*Result, *AppError) {
|
||||
@ -1687,6 +1847,18 @@ func (c *Client) GetTeamStats(teamId string) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetTeamStats will return a team stats object containing the number of users on the team
|
||||
// based on the team id provided. Must be authenticated.
|
||||
func (c *Client) GetTeamByName(teamName string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(fmt.Sprintf("/teams/name/%v", teamName), "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), TeamStatsFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetTeamMembersByIds will return team member objects as an array based on the
|
||||
// team id and a list of user ids provided. Must be authenticated.
|
||||
func (c *Client) GetTeamMembersByIds(teamId string, userIds []string) (*Result, *AppError) {
|
||||
@ -2030,6 +2202,7 @@ func (c *Client) DeleteEmoji(id string) (bool, *AppError) {
|
||||
if r, err := c.DoApiPost(c.GetEmojiRoute()+"/delete", MapToJson(data)); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
c.fillInExtraProperties(r)
|
||||
return c.CheckStatusOK(r), nil
|
||||
}
|
||||
@ -2060,6 +2233,7 @@ func (c *Client) UploadCertificateFile(data []byte, contentType string) *AppErro
|
||||
return AppErrorFromJson(rp.Body)
|
||||
} else {
|
||||
defer closeBody(rp)
|
||||
c.fillInExtraProperties(rp)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -2071,6 +2245,7 @@ func (c *Client) RemoveCertificateFile(filename string) *AppError {
|
||||
return err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
c.fillInExtraProperties(r)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -2082,6 +2257,7 @@ func (c *Client) SamlCertificateStatus(filename string) (map[string]interface{},
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
c.fillInExtraProperties(r)
|
||||
return StringInterfaceFromJson(r.Body), nil
|
||||
}
|
||||
}
|
||||
@ -2110,3 +2286,36 @@ func (c *Client) GetFileInfosForPost(channelId string, postId string, etag strin
|
||||
return FileInfosFromJson(r.Body), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Saves an emoji reaction for a post in the given channel. Returns the saved reaction if successful, otherwise returns an AppError.
|
||||
func (c *Client) SaveReaction(channelId string, reaction *Reaction) (*Reaction, *AppError) {
|
||||
if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/reactions/save", reaction.PostId), reaction.ToJson()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
c.fillInExtraProperties(r)
|
||||
return ReactionFromJson(r.Body), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Removes an emoji reaction for a post in the given channel. Returns nil if successful, otherwise returns an AppError.
|
||||
func (c *Client) DeleteReaction(channelId string, reaction *Reaction) *AppError {
|
||||
if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/reactions/delete", reaction.PostId), reaction.ToJson()); err != nil {
|
||||
return err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
c.fillInExtraProperties(r)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Lists all emoji reactions made for the given post in the given channel. Returns a list of Reactions if successful, otherwise returns an AppError.
|
||||
func (c *Client) ListReactions(channelId string, postId string) ([]*Reaction, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/reactions", postId), "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
c.fillInExtraProperties(r)
|
||||
return ReactionsFromJson(r.Body), nil
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user