github.com/adacta-ru/mattermost-server/v6@v6.0.0/app/slashcommands/command_loadtest.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package slashcommands
     5  
     6  import (
     7  	"io"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"path"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  
    15  	goi18n "github.com/mattermost/go-i18n/i18n"
    16  	"github.com/adacta-ru/mattermost-server/v6/app"
    17  	"github.com/adacta-ru/mattermost-server/v6/mlog"
    18  	"github.com/adacta-ru/mattermost-server/v6/model"
    19  	"github.com/adacta-ru/mattermost-server/v6/utils"
    20  	"github.com/pkg/errors"
    21  )
    22  
    23  var usage = `Mattermost testing commands to help configure the system
    24  
    25  	COMMANDS:
    26  
    27  	Setup - Creates a testing environment in current team.
    28  		/test setup [teams] [fuzz] <Num Channels> <Num Users> <NumPosts>
    29  
    30  		Example:
    31  		/test setup teams fuzz 10 20 50
    32  
    33  	Users - Add a specified number of random users with fuzz text to current team.
    34  		/test users [fuzz] <Min Users> <Max Users>
    35  
    36  		Example:
    37  			/test users fuzz 5 10
    38  
    39  	Channels - Add a specified number of random channels with fuzz text to current team.
    40  		/test channels [fuzz] <Min Channels> <Max Channels>
    41  
    42  		Example:
    43  			/test channels fuzz 5 10
    44  
    45  	ThreadedPost - create a large threaded post
    46          /test threaded_post
    47  
    48  	Posts - Add some random posts with fuzz text to current channel.
    49  		/test posts [fuzz] <Min Posts> <Max Posts> <Max Images>
    50  
    51  		Example:
    52  			/test posts fuzz 5 10 3
    53  
    54  	Post - Add post to a channel as another user.
    55  		/test post u=@username p=passwd c=~channelname t=teamname "message"
    56  
    57  		Example:
    58  			/test post u=@user-1 p=user-1 c=~town-square t=ad-1 "message"
    59  
    60  	Url - Add a post containing the text from a given url to current channel.
    61  		/test url
    62  
    63  		Example:
    64  			/test http://www.example.com/sample_file.md
    65  
    66  	Json - Add a post using the JSON file as payload to the current channel.
    67  	        /test json url
    68  
    69  		Example
    70  		/test json http://www.example.com/sample_body.json
    71  
    72  `
    73  
    74  const (
    75  	CMD_TEST = "test"
    76  )
    77  
    78  var (
    79  	userRE    = regexp.MustCompile(`u=@([^\s]+)`)
    80  	passwdRE  = regexp.MustCompile(`p=([^\s]+)`)
    81  	teamRE    = regexp.MustCompile(`t=([^\s]+)`)
    82  	channelRE = regexp.MustCompile(`c=~([^\s]+)`)
    83  	messageRE = regexp.MustCompile(`"(.*)"`)
    84  )
    85  
    86  type LoadTestProvider struct {
    87  }
    88  
    89  func init() {
    90  	app.RegisterCommandProvider(&LoadTestProvider{})
    91  }
    92  
    93  func (*LoadTestProvider) GetTrigger() string {
    94  	return CMD_TEST
    95  }
    96  
    97  func (*LoadTestProvider) GetCommand(a *app.App, T goi18n.TranslateFunc) *model.Command {
    98  	if !*a.Config().ServiceSettings.EnableTesting {
    99  		return nil
   100  	}
   101  	return &model.Command{
   102  		Trigger:          CMD_TEST,
   103  		AutoComplete:     false,
   104  		AutoCompleteDesc: "Debug Load Testing",
   105  		AutoCompleteHint: "help",
   106  		DisplayName:      "test",
   107  	}
   108  }
   109  
   110  func (lt *LoadTestProvider) DoCommand(a *app.App, args *model.CommandArgs, message string) *model.CommandResponse {
   111  	commandResponse, err := lt.doCommand(a, args, message)
   112  	if err != nil {
   113  		mlog.Error("failed command /"+CMD_TEST, mlog.Err(err))
   114  	}
   115  
   116  	return commandResponse
   117  }
   118  
   119  func (lt *LoadTestProvider) doCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   120  	//This command is only available when EnableTesting is true
   121  	if !*a.Config().ServiceSettings.EnableTesting {
   122  		return &model.CommandResponse{}, nil
   123  	}
   124  
   125  	if strings.HasPrefix(message, "setup") {
   126  		return lt.SetupCommand(a, args, message)
   127  	}
   128  
   129  	if strings.HasPrefix(message, "users") {
   130  		return lt.UsersCommand(a, args, message)
   131  	}
   132  
   133  	if strings.HasPrefix(message, "activate_user") {
   134  		return lt.ActivateUserCommand(a, args, message)
   135  	}
   136  
   137  	if strings.HasPrefix(message, "deactivate_user") {
   138  		return lt.DeActivateUserCommand(a, args, message)
   139  	}
   140  
   141  	if strings.HasPrefix(message, "channels") {
   142  		return lt.ChannelsCommand(a, args, message)
   143  	}
   144  
   145  	if strings.HasPrefix(message, "posts") {
   146  		return lt.PostsCommand(a, args, message)
   147  	}
   148  
   149  	if strings.HasPrefix(message, "post") {
   150  		return lt.PostCommand(a, args, message)
   151  	}
   152  
   153  	if strings.HasPrefix(message, "threaded_post") {
   154  		return lt.ThreadedPostCommand(a, args, message)
   155  	}
   156  
   157  	if strings.HasPrefix(message, "url") {
   158  		return lt.UrlCommand(a, args, message)
   159  	}
   160  
   161  	if strings.HasPrefix(message, "json") {
   162  		return lt.JsonCommand(a, args, message)
   163  	}
   164  
   165  	return lt.HelpCommand(args, message), nil
   166  }
   167  
   168  func (*LoadTestProvider) HelpCommand(args *model.CommandArgs, message string) *model.CommandResponse {
   169  	return &model.CommandResponse{Text: usage, ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   170  }
   171  
   172  func (*LoadTestProvider) SetupCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   173  	tokens := strings.Fields(strings.TrimPrefix(message, "setup"))
   174  	doTeams := contains(tokens, "teams")
   175  	doFuzz := contains(tokens, "fuzz")
   176  
   177  	numArgs := 0
   178  	if doTeams {
   179  		numArgs++
   180  	}
   181  	if doFuzz {
   182  		numArgs++
   183  	}
   184  
   185  	var numTeams int
   186  	var numChannels int
   187  	var numUsers int
   188  	var numPosts int
   189  
   190  	// Defaults
   191  	numTeams = 10
   192  	numChannels = 10
   193  	numUsers = 10
   194  	numPosts = 10
   195  
   196  	if doTeams {
   197  		if (len(tokens) - numArgs) >= 4 {
   198  			numTeams, _ = strconv.Atoi(tokens[numArgs+0])
   199  			numChannels, _ = strconv.Atoi(tokens[numArgs+1])
   200  			numUsers, _ = strconv.Atoi(tokens[numArgs+2])
   201  			numPosts, _ = strconv.Atoi(tokens[numArgs+3])
   202  		}
   203  	} else {
   204  		if (len(tokens) - numArgs) >= 3 {
   205  			numChannels, _ = strconv.Atoi(tokens[numArgs+0])
   206  			numUsers, _ = strconv.Atoi(tokens[numArgs+1])
   207  			numPosts, _ = strconv.Atoi(tokens[numArgs+2])
   208  		}
   209  	}
   210  	client := model.NewAPIv4Client(args.SiteURL)
   211  
   212  	if doTeams {
   213  		if err := CreateBasicUser(a, client); err != nil {
   214  			return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   215  		}
   216  		_, resp := client.Login(BTEST_USER_EMAIL, BTEST_USER_PASSWORD)
   217  		if resp.Error != nil {
   218  			return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, resp.Error
   219  		}
   220  		environment, err := CreateTestEnvironmentWithTeams(
   221  			a,
   222  			client,
   223  			utils.Range{Begin: numTeams, End: numTeams},
   224  			utils.Range{Begin: numChannels, End: numChannels},
   225  			utils.Range{Begin: numUsers, End: numUsers},
   226  			utils.Range{Begin: numPosts, End: numPosts},
   227  			doFuzz)
   228  		if err != nil {
   229  			return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   230  		}
   231  
   232  		mlog.Info("Testing environment created")
   233  		for i := 0; i < len(environment.Teams); i++ {
   234  			mlog.Info("Team Created: " + environment.Teams[i].Name)
   235  			mlog.Info("\t User to login: " + environment.Environments[i].Users[0].Email + ", " + USER_PASSWORD)
   236  		}
   237  	} else {
   238  		team, err := a.Srv().Store.Team().Get(args.TeamId)
   239  		if err != nil {
   240  			return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   241  		}
   242  
   243  		CreateTestEnvironmentInTeam(
   244  			a,
   245  			client,
   246  			team,
   247  			utils.Range{Begin: numChannels, End: numChannels},
   248  			utils.Range{Begin: numUsers, End: numUsers},
   249  			utils.Range{Begin: numPosts, End: numPosts},
   250  			doFuzz)
   251  	}
   252  
   253  	return &model.CommandResponse{Text: "Created environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   254  }
   255  
   256  func (*LoadTestProvider) ActivateUserCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   257  	user_id := strings.TrimSpace(strings.TrimPrefix(message, "activate_user"))
   258  	if err := a.UpdateUserActive(user_id, true); err != nil {
   259  		return &model.CommandResponse{Text: "Failed to activate user", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   260  	}
   261  
   262  	return &model.CommandResponse{Text: "Activated user", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   263  }
   264  
   265  func (*LoadTestProvider) DeActivateUserCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   266  	user_id := strings.TrimSpace(strings.TrimPrefix(message, "deactivate_user"))
   267  	if err := a.UpdateUserActive(user_id, false); err != nil {
   268  		return &model.CommandResponse{Text: "Failed to deactivate user", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   269  	}
   270  
   271  	return &model.CommandResponse{Text: "DeActivated user", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   272  }
   273  
   274  func (*LoadTestProvider) UsersCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   275  	cmd := strings.TrimSpace(strings.TrimPrefix(message, "users"))
   276  
   277  	doFuzz := false
   278  	if strings.Index(cmd, "fuzz") == 0 {
   279  		doFuzz = true
   280  		cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
   281  	}
   282  
   283  	usersr, ok := parseRange(cmd, "")
   284  	if !ok {
   285  		usersr = utils.Range{Begin: 2, End: 5}
   286  	}
   287  
   288  	team, err := a.Srv().Store.Team().Get(args.TeamId)
   289  	if err != nil {
   290  		return &model.CommandResponse{Text: "Failed to add users", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   291  	}
   292  
   293  	client := model.NewAPIv4Client(args.SiteURL)
   294  	userCreator := NewAutoUserCreator(a, client, team)
   295  	userCreator.Fuzzy = doFuzz
   296  	userCreator.CreateTestUsers(usersr)
   297  
   298  	return &model.CommandResponse{Text: "Added users", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   299  }
   300  
   301  func (*LoadTestProvider) ChannelsCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   302  	cmd := strings.TrimSpace(strings.TrimPrefix(message, "channels"))
   303  
   304  	doFuzz := false
   305  	if strings.Index(cmd, "fuzz") == 0 {
   306  		doFuzz = true
   307  		cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
   308  	}
   309  
   310  	channelsr, ok := parseRange(cmd, "")
   311  	if !ok {
   312  		channelsr = utils.Range{Begin: 2, End: 5}
   313  	}
   314  
   315  	team, err := a.Srv().Store.Team().Get(args.TeamId)
   316  	if err != nil {
   317  		return &model.CommandResponse{Text: "Failed to add channels", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   318  	}
   319  
   320  	channelCreator := NewAutoChannelCreator(a, team, args.UserId)
   321  	channelCreator.Fuzzy = doFuzz
   322  	if _, err := channelCreator.CreateTestChannels(channelsr); err != nil {
   323  		return &model.CommandResponse{Text: "Failed to create test channels: " + err.Error(), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   324  	}
   325  
   326  	return &model.CommandResponse{Text: "Added channels", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   327  }
   328  
   329  func (*LoadTestProvider) ThreadedPostCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   330  	var usernames []string
   331  	options := &model.UserGetOptions{InTeamId: args.TeamId, Page: 0, PerPage: 1000}
   332  	if profileUsers, err := a.Srv().Store.User().GetProfiles(options); err == nil {
   333  		usernames = make([]string, len(profileUsers))
   334  		i := 0
   335  		for _, userprof := range profileUsers {
   336  			usernames[i] = userprof.Username
   337  			i++
   338  		}
   339  	}
   340  
   341  	testPoster := NewAutoPostCreator(a, args.ChannelId, args.UserId)
   342  	testPoster.Fuzzy = true
   343  	testPoster.Users = usernames
   344  	rpost, err2 := testPoster.CreateRandomPost()
   345  	if err2 != nil {
   346  		return &model.CommandResponse{Text: "Failed to create a post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err2
   347  	}
   348  	for i := 0; i < 1000; i++ {
   349  		testPoster.CreateRandomPostNested(rpost.Id, rpost.Id)
   350  	}
   351  
   352  	return &model.CommandResponse{Text: "Added threaded post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   353  }
   354  
   355  func (*LoadTestProvider) PostsCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   356  	cmd := strings.TrimSpace(strings.TrimPrefix(message, "posts"))
   357  
   358  	doFuzz := false
   359  	if strings.Index(cmd, "fuzz") == 0 {
   360  		doFuzz = true
   361  		cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
   362  	}
   363  
   364  	postsr, ok := parseRange(cmd, "")
   365  	if !ok {
   366  		postsr = utils.Range{Begin: 20, End: 30}
   367  	}
   368  
   369  	tokens := strings.Fields(cmd)
   370  	rimages := utils.Range{Begin: 0, End: 0}
   371  	if len(tokens) >= 3 {
   372  		if numImages, err := strconv.Atoi(tokens[2]); err == nil {
   373  			rimages = utils.Range{Begin: numImages, End: numImages}
   374  		}
   375  	}
   376  
   377  	var usernames []string
   378  	options := &model.UserGetOptions{InTeamId: args.TeamId, Page: 0, PerPage: 1000}
   379  	if profileUsers, err := a.Srv().Store.User().GetProfiles(options); err == nil {
   380  		usernames = make([]string, len(profileUsers))
   381  		i := 0
   382  		for _, userprof := range profileUsers {
   383  			usernames[i] = userprof.Username
   384  			i++
   385  		}
   386  	}
   387  
   388  	testPoster := NewAutoPostCreator(a, args.ChannelId, args.UserId)
   389  	testPoster.Fuzzy = doFuzz
   390  	testPoster.Users = usernames
   391  
   392  	numImages := utils.RandIntFromRange(rimages)
   393  	numPosts := utils.RandIntFromRange(postsr)
   394  	for i := 0; i < numPosts; i++ {
   395  		testPoster.HasImage = (i < numImages)
   396  		_, err := testPoster.CreateRandomPost()
   397  		if err != nil {
   398  			return &model.CommandResponse{Text: "Failed to add posts", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   399  		}
   400  
   401  	}
   402  
   403  	return &model.CommandResponse{Text: "Added posts", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   404  }
   405  
   406  func getMatch(re *regexp.Regexp, text string) string {
   407  	if match := re.FindStringSubmatch(text); match != nil {
   408  		return match[1]
   409  	}
   410  
   411  	return ""
   412  }
   413  
   414  func (*LoadTestProvider) PostCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   415  	textMessage := getMatch(messageRE, message)
   416  	if textMessage == "" {
   417  		return &model.CommandResponse{Text: "No message to post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   418  	}
   419  
   420  	teamName := getMatch(teamRE, message)
   421  	team, err := a.GetTeamByName(teamName)
   422  	if err != nil {
   423  		return &model.CommandResponse{Text: "Failed to get a team", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   424  	}
   425  
   426  	channelName := getMatch(channelRE, message)
   427  	channel, err := a.GetChannelByName(channelName, team.Id, true)
   428  	if err != nil {
   429  		return &model.CommandResponse{Text: "Failed to get a channel", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   430  	}
   431  
   432  	passwd := getMatch(passwdRE, message)
   433  	username := getMatch(userRE, message)
   434  	user, err := a.GetUserByUsername(username)
   435  	if err != nil {
   436  		return &model.CommandResponse{Text: "Failed to get a user", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   437  	}
   438  
   439  	client := model.NewAPIv4Client(args.SiteURL)
   440  	_, resp := client.LoginById(user.Id, passwd)
   441  	if resp.Error != nil {
   442  		return &model.CommandResponse{Text: "Failed to login a user", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, resp.Error
   443  	}
   444  
   445  	post := &model.Post{
   446  		ChannelId: channel.Id,
   447  		Message:   textMessage,
   448  	}
   449  	_, resp = client.CreatePost(post)
   450  	if resp.Error != nil {
   451  		return &model.CommandResponse{Text: "Failed to create a post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, resp.Error
   452  	}
   453  
   454  	return &model.CommandResponse{Text: "Added a post to " + channel.DisplayName, ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   455  }
   456  
   457  func (*LoadTestProvider) UrlCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   458  	url := strings.TrimSpace(strings.TrimPrefix(message, "url"))
   459  	if len(url) == 0 {
   460  		return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   461  	}
   462  
   463  	// provide a shortcut to easily access tests stored in doc/developer/tests
   464  	if !strings.HasPrefix(url, "http") {
   465  		url = "https://raw.githubusercontent.com/mattermost/mattermost-server/master/tests/" + url
   466  
   467  		if path.Ext(url) == "" {
   468  			url += ".md"
   469  		}
   470  	}
   471  
   472  	r, err := http.Get(url)
   473  	if err != nil {
   474  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   475  	}
   476  	defer func() {
   477  		io.Copy(ioutil.Discard, r.Body)
   478  		r.Body.Close()
   479  	}()
   480  
   481  	if r.StatusCode > 400 {
   482  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, errors.Errorf("unexpected status code %d", r.StatusCode)
   483  	}
   484  
   485  	bytes := make([]byte, 4000)
   486  
   487  	// break contents into 4000 byte posts
   488  	for {
   489  		length, err := r.Body.Read(bytes)
   490  		if err != nil && err != io.EOF {
   491  			return &model.CommandResponse{Text: "Encountered error reading file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   492  		}
   493  
   494  		if length == 0 {
   495  			break
   496  		}
   497  
   498  		post := &model.Post{}
   499  		post.Message = string(bytes[:length])
   500  		post.ChannelId = args.ChannelId
   501  		post.UserId = args.UserId
   502  
   503  		if _, err := a.CreatePostMissingChannel(post, false); err != nil {
   504  			return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   505  		}
   506  	}
   507  
   508  	return &model.CommandResponse{Text: "Loaded data", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   509  }
   510  
   511  func (*LoadTestProvider) JsonCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   512  	url := strings.TrimSpace(strings.TrimPrefix(message, "json"))
   513  	if len(url) == 0 {
   514  		return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   515  	}
   516  
   517  	// provide a shortcut to easily access tests stored in doc/developer/tests
   518  	if !strings.HasPrefix(url, "http") {
   519  		url = "https://raw.githubusercontent.com/mattermost/mattermost-server/master/tests/" + url
   520  
   521  		if path.Ext(url) == "" {
   522  			url += ".json"
   523  		}
   524  	}
   525  
   526  	r, err := http.Get(url)
   527  	if err != nil {
   528  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   529  	}
   530  
   531  	if r.StatusCode > 400 {
   532  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, errors.Errorf("unexpected status code %d", r.StatusCode)
   533  	}
   534  	defer func() {
   535  		io.Copy(ioutil.Discard, r.Body)
   536  		r.Body.Close()
   537  	}()
   538  
   539  	post := model.PostFromJson(r.Body)
   540  	post.ChannelId = args.ChannelId
   541  	post.UserId = args.UserId
   542  	if post.Message == "" {
   543  		post.Message = message
   544  	}
   545  
   546  	if _, err := a.CreatePostMissingChannel(post, false); err != nil {
   547  		return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   548  	}
   549  
   550  	return &model.CommandResponse{Text: "Loaded data", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   551  }
   552  
   553  func parseRange(command string, cmd string) (utils.Range, bool) {
   554  	tokens := strings.Fields(strings.TrimPrefix(command, cmd))
   555  	var begin int
   556  	var end int
   557  	var err1 error
   558  	var err2 error
   559  	switch {
   560  	case len(tokens) == 1:
   561  		begin, err1 = strconv.Atoi(tokens[0])
   562  		end = begin
   563  		if err1 != nil {
   564  			return utils.Range{Begin: 0, End: 0}, false
   565  		}
   566  	case len(tokens) >= 2:
   567  		begin, err1 = strconv.Atoi(tokens[0])
   568  		end, err2 = strconv.Atoi(tokens[1])
   569  		if err1 != nil || err2 != nil {
   570  			return utils.Range{Begin: 0, End: 0}, false
   571  		}
   572  	default:
   573  		return utils.Range{Begin: 0, End: 0}, false
   574  	}
   575  	return utils.Range{Begin: begin, End: end}, true
   576  }
   577  
   578  func contains(items []string, token string) bool {
   579  	for _, elem := range items {
   580  		if elem == token {
   581  			return true
   582  		}
   583  	}
   584  	return false
   585  }