github.com/levb/mattermost-server@v5.3.1+incompatible/cmd/mattermost/commands/sampledata.go (about)

     1  // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package commands
     5  
     6  import (
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"math/rand"
    12  	"os"
    13  	"path"
    14  	"sort"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/icrowley/fake"
    19  	"github.com/mattermost/mattermost-server/app"
    20  	"github.com/spf13/cobra"
    21  )
    22  
    23  var SampleDataCmd = &cobra.Command{
    24  	Use:   "sampledata",
    25  	Short: "Generate sample data",
    26  	RunE:  sampleDataCmdF,
    27  }
    28  
    29  func init() {
    30  	SampleDataCmd.Flags().Int64P("seed", "s", 1, "Seed used for generating the random data (Different seeds generate different data).")
    31  	SampleDataCmd.Flags().IntP("teams", "t", 2, "The number of sample teams.")
    32  	SampleDataCmd.Flags().Int("channels-per-team", 10, "The number of sample channels per team.")
    33  	SampleDataCmd.Flags().IntP("users", "u", 15, "The number of sample users.")
    34  	SampleDataCmd.Flags().Int("team-memberships", 2, "The number of sample team memberships per user.")
    35  	SampleDataCmd.Flags().Int("channel-memberships", 5, "The number of sample channel memberships per user in a team.")
    36  	SampleDataCmd.Flags().Int("posts-per-channel", 100, "The number of sample post per channel.")
    37  	SampleDataCmd.Flags().Int("direct-channels", 30, "The number of sample direct message channels.")
    38  	SampleDataCmd.Flags().Int("posts-per-direct-channel", 15, "The number of sample posts per direct message channel.")
    39  	SampleDataCmd.Flags().Int("group-channels", 15, "The number of sample group message channels.")
    40  	SampleDataCmd.Flags().Int("posts-per-group-channel", 30, "The number of sample posts per group message channel.")
    41  	SampleDataCmd.Flags().IntP("workers", "w", 2, "How many workers to run during the import.")
    42  	SampleDataCmd.Flags().String("profile-images", "", "Optional. Path to folder with images to randomly pick as user profile image.")
    43  	SampleDataCmd.Flags().StringP("bulk", "b", "", "Optional. Path to write a JSONL bulk file instead of loading into the database.")
    44  	RootCmd.AddCommand(SampleDataCmd)
    45  }
    46  
    47  func sliceIncludes(vs []string, t string) bool {
    48  	for _, v := range vs {
    49  		if v == t {
    50  			return true
    51  		}
    52  	}
    53  	return false
    54  }
    55  
    56  func randomPastTime(seconds int) int64 {
    57  	now := time.Now()
    58  	today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.FixedZone("UTC", 0))
    59  	return (today.Unix() * 1000) - int64(rand.Intn(seconds*1000))
    60  }
    61  
    62  func sortedRandomDates(size int) []int64 {
    63  	dates := make([]int64, size)
    64  	for i := 0; i < size; i++ {
    65  		dates[i] = randomPastTime(50000)
    66  	}
    67  	sort.Slice(dates, func(a, b int) bool { return dates[a] < dates[b] })
    68  	return dates
    69  }
    70  
    71  func randomEmoji() string {
    72  	emojis := []string{"+1", "-1", "heart", "blush"}
    73  	return emojis[rand.Intn(len(emojis))]
    74  }
    75  
    76  func randomReaction(users []string, parentCreateAt int64) app.ReactionImportData {
    77  	user := users[rand.Intn(len(users))]
    78  	emoji := randomEmoji()
    79  	date := parentCreateAt + int64(rand.Intn(100000))
    80  	return app.ReactionImportData{
    81  		User:      &user,
    82  		EmojiName: &emoji,
    83  		CreateAt:  &date,
    84  	}
    85  }
    86  
    87  func randomReply(users []string, parentCreateAt int64) app.ReplyImportData {
    88  	user := users[rand.Intn(len(users))]
    89  	message := randomMessage(users)
    90  	date := parentCreateAt + int64(rand.Intn(100000))
    91  	return app.ReplyImportData{
    92  		User:     &user,
    93  		Message:  &message,
    94  		CreateAt: &date,
    95  	}
    96  }
    97  
    98  func randomMessage(users []string) string {
    99  	var message string
   100  	switch rand.Intn(30) {
   101  	case 0:
   102  		mention := users[rand.Intn(len(users))]
   103  		message = "@" + mention + " " + fake.Sentence()
   104  	case 1:
   105  		switch rand.Intn(2) {
   106  		case 0:
   107  			mattermostVideos := []string{"Q4MgnxbpZas", "BFo7E9-Kc_E", "LsMLR-BHsKg", "MRmGDhlMhNA", "mUOPxT7VgWc"}
   108  			message = "https://www.youtube.com/watch?v=" + mattermostVideos[rand.Intn(len(mattermostVideos))]
   109  		case 1:
   110  			mattermostTweets := []string{"943119062334353408", "949370809528832005", "948539688171819009", "939122439115681792", "938061722027425797"}
   111  			message = "https://twitter.com/mattermosthq/status/" + mattermostTweets[rand.Intn(len(mattermostTweets))]
   112  		}
   113  	case 2:
   114  		message = ""
   115  		if rand.Intn(2) == 0 {
   116  			message += fake.Sentence()
   117  		}
   118  		for i := 0; i < rand.Intn(4)+1; i++ {
   119  			message += "\n  * " + fake.Word()
   120  		}
   121  	default:
   122  		if rand.Intn(2) == 0 {
   123  			message = fake.Sentence()
   124  		} else {
   125  			message = fake.Paragraph()
   126  		}
   127  		if rand.Intn(3) == 0 {
   128  			message += "\n" + fake.Sentence()
   129  		}
   130  		if rand.Intn(3) == 0 {
   131  			message += "\n" + fake.Sentence()
   132  		}
   133  		if rand.Intn(3) == 0 {
   134  			message += "\n" + fake.Sentence()
   135  		}
   136  	}
   137  	return message
   138  }
   139  
   140  func sampleDataCmdF(command *cobra.Command, args []string) error {
   141  	a, err := InitDBCommandContextCobra(command)
   142  	if err != nil {
   143  		return err
   144  	}
   145  	defer a.Shutdown()
   146  
   147  	seed, err := command.Flags().GetInt64("seed")
   148  	if err != nil {
   149  		return errors.New("Invalid seed parameter")
   150  	}
   151  	bulk, err := command.Flags().GetString("bulk")
   152  	if err != nil {
   153  		return errors.New("Invalid bulk parameter")
   154  	}
   155  	teams, err := command.Flags().GetInt("teams")
   156  	if err != nil || teams < 0 {
   157  		return errors.New("Invalid teams parameter")
   158  	}
   159  	channelsPerTeam, err := command.Flags().GetInt("channels-per-team")
   160  	if err != nil || channelsPerTeam < 0 {
   161  		return errors.New("Invalid channels-per-team parameter")
   162  	}
   163  	users, err := command.Flags().GetInt("users")
   164  	if err != nil || users < 0 {
   165  		return errors.New("Invalid users parameter")
   166  	}
   167  	teamMemberships, err := command.Flags().GetInt("team-memberships")
   168  	if err != nil || teamMemberships < 0 {
   169  		return errors.New("Invalid team-memberships parameter")
   170  	}
   171  	channelMemberships, err := command.Flags().GetInt("channel-memberships")
   172  	if err != nil || channelMemberships < 0 {
   173  		return errors.New("Invalid channel-memberships parameter")
   174  	}
   175  	postsPerChannel, err := command.Flags().GetInt("posts-per-channel")
   176  	if err != nil || postsPerChannel < 0 {
   177  		return errors.New("Invalid posts-per-channel parameter")
   178  	}
   179  	directChannels, err := command.Flags().GetInt("direct-channels")
   180  	if err != nil || directChannels < 0 {
   181  		return errors.New("Invalid direct-channels parameter")
   182  	}
   183  	postsPerDirectChannel, err := command.Flags().GetInt("posts-per-direct-channel")
   184  	if err != nil || postsPerDirectChannel < 0 {
   185  		return errors.New("Invalid posts-per-direct-channel parameter")
   186  	}
   187  	groupChannels, err := command.Flags().GetInt("group-channels")
   188  	if err != nil || groupChannels < 0 {
   189  		return errors.New("Invalid group-channels parameter")
   190  	}
   191  	postsPerGroupChannel, err := command.Flags().GetInt("posts-per-group-channel")
   192  	if err != nil || postsPerGroupChannel < 0 {
   193  		return errors.New("Invalid posts-per-group-channel parameter")
   194  	}
   195  	workers, err := command.Flags().GetInt("workers")
   196  	if err != nil {
   197  		return errors.New("Invalid workers parameter")
   198  	}
   199  	profileImagesPath, err := command.Flags().GetString("profile-images")
   200  	if err != nil {
   201  		return errors.New("Invalid profile-images parameter")
   202  	}
   203  	profileImages := []string{}
   204  	if profileImagesPath != "" {
   205  		profileImagesStat, err := os.Stat(profileImagesPath)
   206  		if os.IsNotExist(err) {
   207  			return errors.New("Profile images folder doesn't exists.")
   208  		}
   209  		if !profileImagesStat.IsDir() {
   210  			return errors.New("profile-images parameters must be a folder path.")
   211  		}
   212  		profileImagesFiles, err := ioutil.ReadDir(profileImagesPath)
   213  		if err != nil {
   214  			return errors.New("Invalid profile-images parameter")
   215  		}
   216  		for _, profileImage := range profileImagesFiles {
   217  			profileImages = append(profileImages, path.Join(profileImagesPath, profileImage.Name()))
   218  		}
   219  		sort.Strings(profileImages)
   220  	}
   221  
   222  	if workers < 1 {
   223  		return errors.New("You must have at least one worker.")
   224  	}
   225  	if teamMemberships > teams {
   226  		return errors.New("You can't have more team memberships than teams.")
   227  	}
   228  	if channelMemberships > channelsPerTeam {
   229  		return errors.New("You can't have more channel memberships than channels per team.")
   230  	}
   231  
   232  	var bulkFile *os.File
   233  	switch bulk {
   234  	case "":
   235  		bulkFile, err = ioutil.TempFile("", ".mattermost-sample-data-")
   236  		defer os.Remove(bulkFile.Name())
   237  		if err != nil {
   238  			return errors.New("Unable to open temporary file.")
   239  		}
   240  	case "-":
   241  		bulkFile = os.Stdout
   242  	default:
   243  		bulkFile, err = os.OpenFile(bulk, os.O_RDWR|os.O_CREATE, 0755)
   244  		if err != nil {
   245  			return errors.New("Unable to write into the \"" + bulk + "\" file.")
   246  		}
   247  	}
   248  
   249  	encoder := json.NewEncoder(bulkFile)
   250  	version := 1
   251  	encoder.Encode(app.LineImportData{Type: "version", Version: &version})
   252  
   253  	fake.Seed(seed)
   254  	rand.Seed(seed)
   255  
   256  	teamsAndChannels := make(map[string][]string)
   257  	for i := 0; i < teams; i++ {
   258  		teamLine := createTeam(i)
   259  		teamsAndChannels[*teamLine.Team.Name] = []string{}
   260  		encoder.Encode(teamLine)
   261  	}
   262  
   263  	teamsList := []string{}
   264  	for teamName := range teamsAndChannels {
   265  		teamsList = append(teamsList, teamName)
   266  	}
   267  	sort.Strings(teamsList)
   268  
   269  	for _, teamName := range teamsList {
   270  		for i := 0; i < channelsPerTeam; i++ {
   271  			channelLine := createChannel(i, teamName)
   272  			teamsAndChannels[teamName] = append(teamsAndChannels[teamName], *channelLine.Channel.Name)
   273  			encoder.Encode(channelLine)
   274  		}
   275  	}
   276  
   277  	allUsers := []string{}
   278  	for i := 0; i < users; i++ {
   279  		userLine := createUser(i, teamMemberships, channelMemberships, teamsAndChannels, profileImages)
   280  		encoder.Encode(userLine)
   281  		allUsers = append(allUsers, *userLine.User.Username)
   282  	}
   283  
   284  	for team, channels := range teamsAndChannels {
   285  		for _, channel := range channels {
   286  			dates := sortedRandomDates(postsPerChannel)
   287  
   288  			for i := 0; i < postsPerChannel; i++ {
   289  				postLine := createPost(team, channel, allUsers, dates[i])
   290  				encoder.Encode(postLine)
   291  			}
   292  		}
   293  	}
   294  
   295  	for i := 0; i < directChannels; i++ {
   296  		user1 := allUsers[rand.Intn(len(allUsers))]
   297  		user2 := allUsers[rand.Intn(len(allUsers))]
   298  		channelLine := createDirectChannel([]string{user1, user2})
   299  		encoder.Encode(channelLine)
   300  
   301  		dates := sortedRandomDates(postsPerDirectChannel)
   302  		for j := 0; j < postsPerDirectChannel; j++ {
   303  			postLine := createDirectPost([]string{user1, user2}, dates[j])
   304  			encoder.Encode(postLine)
   305  		}
   306  	}
   307  
   308  	for i := 0; i < groupChannels; i++ {
   309  		users := []string{}
   310  		totalUsers := 3 + rand.Intn(3)
   311  		for len(users) < totalUsers {
   312  			user := allUsers[rand.Intn(len(allUsers))]
   313  			if !sliceIncludes(users, user) {
   314  				users = append(users, user)
   315  			}
   316  		}
   317  		channelLine := createDirectChannel(users)
   318  		encoder.Encode(channelLine)
   319  
   320  		dates := sortedRandomDates(postsPerGroupChannel)
   321  		for j := 0; j < postsPerGroupChannel; j++ {
   322  			postLine := createDirectPost(users, dates[j])
   323  			encoder.Encode(postLine)
   324  		}
   325  	}
   326  
   327  	if bulk == "" {
   328  		_, err := bulkFile.Seek(0, 0)
   329  		if err != nil {
   330  			return errors.New("Unable to read correctly the temporary file.")
   331  		}
   332  		importErr, lineNumber := a.BulkImport(bulkFile, false, workers)
   333  		if importErr != nil {
   334  			return fmt.Errorf("%s: %s, %s (line: %d)", importErr.Where, importErr.Message, importErr.DetailedError, lineNumber)
   335  		}
   336  	} else if bulk != "-" {
   337  		err := bulkFile.Close()
   338  		if err != nil {
   339  			return errors.New("Unable to close correctly the output file")
   340  		}
   341  	}
   342  
   343  	return nil
   344  }
   345  
   346  func createUser(idx int, teamMemberships int, channelMemberships int, teamsAndChannels map[string][]string, profileImages []string) app.LineImportData {
   347  	password := fmt.Sprintf("user-%d", idx)
   348  	email := fmt.Sprintf("user-%d@sample.mattermost.com", idx)
   349  	firstName := fake.FirstName()
   350  	lastName := fake.LastName()
   351  	username := fmt.Sprintf("%s.%s", strings.ToLower(firstName), strings.ToLower(lastName))
   352  	if idx == 0 {
   353  		username = "sysadmin"
   354  		password = "sysadmin"
   355  		email = "sysadmin@sample.mattermost.com"
   356  	} else if idx == 1 {
   357  		username = "user-1"
   358  	}
   359  	position := fake.JobTitle()
   360  	roles := "system_user"
   361  	if idx%5 == 0 {
   362  		roles = "system_admin system_user"
   363  	}
   364  
   365  	// The 75% of the users have custom profile image
   366  	var profileImage *string = nil
   367  	if rand.Intn(4) != 0 {
   368  		profileImageSelector := rand.Int()
   369  		if len(profileImages) > 0 {
   370  			profileImage = &profileImages[profileImageSelector%len(profileImages)]
   371  		}
   372  	}
   373  
   374  	useMilitaryTime := "false"
   375  	if idx != 0 && rand.Intn(2) == 0 {
   376  		useMilitaryTime = "true"
   377  	}
   378  
   379  	collapsePreviews := "false"
   380  	if idx != 0 && rand.Intn(2) == 0 {
   381  		collapsePreviews = "true"
   382  	}
   383  
   384  	messageDisplay := "clean"
   385  	if idx != 0 && rand.Intn(2) == 0 {
   386  		messageDisplay = "compact"
   387  	}
   388  
   389  	channelDisplayMode := "full"
   390  	if idx != 0 && rand.Intn(2) == 0 {
   391  		channelDisplayMode = "centered"
   392  	}
   393  
   394  	// Some users has nickname
   395  	nickname := ""
   396  	if rand.Intn(5) == 0 {
   397  		nickname = fake.Company()
   398  	}
   399  
   400  	// Half of users skip tutorial
   401  	tutorialStep := "999"
   402  	switch rand.Intn(6) {
   403  	case 1:
   404  		tutorialStep = "1"
   405  	case 2:
   406  		tutorialStep = "2"
   407  	case 3:
   408  		tutorialStep = "3"
   409  	}
   410  
   411  	teams := []app.UserTeamImportData{}
   412  	possibleTeams := []string{}
   413  	for teamName := range teamsAndChannels {
   414  		possibleTeams = append(possibleTeams, teamName)
   415  	}
   416  	sort.Strings(possibleTeams)
   417  	for x := 0; x < teamMemberships; x++ {
   418  		if len(possibleTeams) == 0 {
   419  			break
   420  		}
   421  		position := rand.Intn(len(possibleTeams))
   422  		team := possibleTeams[position]
   423  		possibleTeams = append(possibleTeams[:position], possibleTeams[position+1:]...)
   424  		if teamChannels, err := teamsAndChannels[team]; err {
   425  			teams = append(teams, createTeamMembership(channelMemberships, teamChannels, &team))
   426  		}
   427  	}
   428  
   429  	user := app.UserImportData{
   430  		ProfileImage:       profileImage,
   431  		Username:           &username,
   432  		Email:              &email,
   433  		Password:           &password,
   434  		Nickname:           &nickname,
   435  		FirstName:          &firstName,
   436  		LastName:           &lastName,
   437  		Position:           &position,
   438  		Roles:              &roles,
   439  		Teams:              &teams,
   440  		UseMilitaryTime:    &useMilitaryTime,
   441  		CollapsePreviews:   &collapsePreviews,
   442  		MessageDisplay:     &messageDisplay,
   443  		ChannelDisplayMode: &channelDisplayMode,
   444  		TutorialStep:       &tutorialStep,
   445  	}
   446  	return app.LineImportData{
   447  		Type: "user",
   448  		User: &user,
   449  	}
   450  }
   451  
   452  func createTeamMembership(numOfchannels int, teamChannels []string, teamName *string) app.UserTeamImportData {
   453  	roles := "team_user"
   454  	if rand.Intn(5) == 0 {
   455  		roles = "team_user team_admin"
   456  	}
   457  	channels := []app.UserChannelImportData{}
   458  	teamChannelsCopy := append([]string(nil), teamChannels...)
   459  	for x := 0; x < numOfchannels; x++ {
   460  		if len(teamChannelsCopy) == 0 {
   461  			break
   462  		}
   463  		position := rand.Intn(len(teamChannelsCopy))
   464  		channelName := teamChannelsCopy[position]
   465  		teamChannelsCopy = append(teamChannelsCopy[:position], teamChannelsCopy[position+1:]...)
   466  		channels = append(channels, createChannelMembership(channelName))
   467  	}
   468  
   469  	return app.UserTeamImportData{
   470  		Name:     teamName,
   471  		Roles:    &roles,
   472  		Channels: &channels,
   473  	}
   474  }
   475  
   476  func createChannelMembership(channelName string) app.UserChannelImportData {
   477  	roles := "channel_user"
   478  	if rand.Intn(5) == 0 {
   479  		roles = "channel_user channel_admin"
   480  	}
   481  	favorite := rand.Intn(5) == 0
   482  
   483  	return app.UserChannelImportData{
   484  		Name:     &channelName,
   485  		Roles:    &roles,
   486  		Favorite: &favorite,
   487  	}
   488  }
   489  
   490  func createTeam(idx int) app.LineImportData {
   491  	displayName := fake.Word()
   492  	name := fmt.Sprintf("%s-%d", fake.Word(), idx)
   493  	allowOpenInvite := rand.Intn(2) == 0
   494  
   495  	description := fake.Paragraph()
   496  	if len(description) > 255 {
   497  		description = description[0:255]
   498  	}
   499  
   500  	teamType := "O"
   501  	if rand.Intn(2) == 0 {
   502  		teamType = "I"
   503  	}
   504  
   505  	team := app.TeamImportData{
   506  		DisplayName:     &displayName,
   507  		Name:            &name,
   508  		AllowOpenInvite: &allowOpenInvite,
   509  		Description:     &description,
   510  		Type:            &teamType,
   511  	}
   512  	return app.LineImportData{
   513  		Type: "team",
   514  		Team: &team,
   515  	}
   516  }
   517  
   518  func createChannel(idx int, teamName string) app.LineImportData {
   519  	displayName := fake.Word()
   520  	name := fmt.Sprintf("%s-%d", fake.Word(), idx)
   521  	header := fake.Paragraph()
   522  	purpose := fake.Paragraph()
   523  
   524  	if len(purpose) > 250 {
   525  		purpose = purpose[0:250]
   526  	}
   527  
   528  	channelType := "P"
   529  	if rand.Intn(2) == 0 {
   530  		channelType = "O"
   531  	}
   532  
   533  	channel := app.ChannelImportData{
   534  		Team:        &teamName,
   535  		Name:        &name,
   536  		DisplayName: &displayName,
   537  		Type:        &channelType,
   538  		Header:      &header,
   539  		Purpose:     &purpose,
   540  	}
   541  	return app.LineImportData{
   542  		Type:    "channel",
   543  		Channel: &channel,
   544  	}
   545  }
   546  
   547  func createPost(team string, channel string, allUsers []string, createAt int64) app.LineImportData {
   548  	message := randomMessage(allUsers)
   549  	create_at := createAt
   550  	user := allUsers[rand.Intn(len(allUsers))]
   551  
   552  	// Some messages are flagged by an user
   553  	flagged_by := []string{}
   554  	if rand.Intn(10) == 0 {
   555  		flagged_by = append(flagged_by, allUsers[rand.Intn(len(allUsers))])
   556  	}
   557  
   558  	reactions := []app.ReactionImportData{}
   559  	if rand.Intn(10) == 0 {
   560  		for {
   561  			reactions = append(reactions, randomReaction(allUsers, create_at))
   562  			if rand.Intn(3) == 0 {
   563  				break
   564  			}
   565  		}
   566  	}
   567  
   568  	replies := []app.ReplyImportData{}
   569  	if rand.Intn(10) == 0 {
   570  		for {
   571  			replies = append(replies, randomReply(allUsers, create_at))
   572  			if rand.Intn(4) == 0 {
   573  				break
   574  			}
   575  		}
   576  	}
   577  
   578  	post := app.PostImportData{
   579  		Team:      &team,
   580  		Channel:   &channel,
   581  		User:      &user,
   582  		Message:   &message,
   583  		CreateAt:  &create_at,
   584  		FlaggedBy: &flagged_by,
   585  		Reactions: &reactions,
   586  		Replies:   &replies,
   587  	}
   588  	return app.LineImportData{
   589  		Type: "post",
   590  		Post: &post,
   591  	}
   592  }
   593  
   594  func createDirectChannel(members []string) app.LineImportData {
   595  	header := fake.Sentence()
   596  
   597  	channel := app.DirectChannelImportData{
   598  		Members: &members,
   599  		Header:  &header,
   600  	}
   601  	return app.LineImportData{
   602  		Type:          "direct_channel",
   603  		DirectChannel: &channel,
   604  	}
   605  }
   606  
   607  func createDirectPost(members []string, createAt int64) app.LineImportData {
   608  	message := randomMessage(members)
   609  	create_at := createAt
   610  	user := members[rand.Intn(len(members))]
   611  
   612  	// Some messages are flagged by an user
   613  	flagged_by := []string{}
   614  	if rand.Intn(10) == 0 {
   615  		flagged_by = append(flagged_by, members[rand.Intn(len(members))])
   616  	}
   617  
   618  	reactions := []app.ReactionImportData{}
   619  	if rand.Intn(10) == 0 {
   620  		for {
   621  			reactions = append(reactions, randomReaction(members, create_at))
   622  			if rand.Intn(3) == 0 {
   623  				break
   624  			}
   625  		}
   626  	}
   627  
   628  	replies := []app.ReplyImportData{}
   629  	if rand.Intn(10) == 0 {
   630  		for {
   631  			replies = append(replies, randomReply(members, create_at))
   632  			if rand.Intn(4) == 0 {
   633  				break
   634  			}
   635  		}
   636  	}
   637  
   638  	post := app.DirectPostImportData{
   639  		ChannelMembers: &members,
   640  		User:           &user,
   641  		Message:        &message,
   642  		CreateAt:       &create_at,
   643  		FlaggedBy:      &flagged_by,
   644  		Reactions:      &reactions,
   645  		Replies:        &replies,
   646  	}
   647  	return app.LineImportData{
   648  		Type:       "direct_post",
   649  		DirectPost: &post,
   650  	}
   651  }