github.com/space0122/mattermost-server@v5.11.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  		var profileImagesStat os.FileInfo
   206  		profileImagesStat, err = os.Stat(profileImagesPath)
   207  		if os.IsNotExist(err) {
   208  			return errors.New("Profile images folder doesn't exists.")
   209  		}
   210  		if !profileImagesStat.IsDir() {
   211  			return errors.New("profile-images parameters must be a folder path.")
   212  		}
   213  		var profileImagesFiles []os.FileInfo
   214  		profileImagesFiles, err = ioutil.ReadDir(profileImagesPath)
   215  		if err != nil {
   216  			return errors.New("Invalid profile-images parameter")
   217  		}
   218  		for _, profileImage := range profileImagesFiles {
   219  			profileImages = append(profileImages, path.Join(profileImagesPath, profileImage.Name()))
   220  		}
   221  		sort.Strings(profileImages)
   222  	}
   223  
   224  	if workers < 1 {
   225  		return errors.New("You must have at least one worker.")
   226  	}
   227  	if teamMemberships > teams {
   228  		return errors.New("You can't have more team memberships than teams.")
   229  	}
   230  	if channelMemberships > channelsPerTeam {
   231  		return errors.New("You can't have more channel memberships than channels per team.")
   232  	}
   233  
   234  	var bulkFile *os.File
   235  	switch bulk {
   236  	case "":
   237  		bulkFile, err = ioutil.TempFile("", ".mattermost-sample-data-")
   238  		defer os.Remove(bulkFile.Name())
   239  		if err != nil {
   240  			return errors.New("Unable to open temporary file.")
   241  		}
   242  	case "-":
   243  		bulkFile = os.Stdout
   244  	default:
   245  		bulkFile, err = os.OpenFile(bulk, os.O_RDWR|os.O_CREATE, 0755)
   246  		if err != nil {
   247  			return errors.New("Unable to write into the \"" + bulk + "\" file.")
   248  		}
   249  	}
   250  
   251  	encoder := json.NewEncoder(bulkFile)
   252  	version := 1
   253  	encoder.Encode(app.LineImportData{Type: "version", Version: &version})
   254  
   255  	fake.Seed(seed)
   256  	rand.Seed(seed)
   257  
   258  	teamsAndChannels := make(map[string][]string)
   259  	for i := 0; i < teams; i++ {
   260  		teamLine := createTeam(i)
   261  		teamsAndChannels[*teamLine.Team.Name] = []string{}
   262  		encoder.Encode(teamLine)
   263  	}
   264  
   265  	teamsList := []string{}
   266  	for teamName := range teamsAndChannels {
   267  		teamsList = append(teamsList, teamName)
   268  	}
   269  	sort.Strings(teamsList)
   270  
   271  	for _, teamName := range teamsList {
   272  		for i := 0; i < channelsPerTeam; i++ {
   273  			channelLine := createChannel(i, teamName)
   274  			teamsAndChannels[teamName] = append(teamsAndChannels[teamName], *channelLine.Channel.Name)
   275  			encoder.Encode(channelLine)
   276  		}
   277  	}
   278  
   279  	allUsers := []string{}
   280  	for i := 0; i < users; i++ {
   281  		userLine := createUser(i, teamMemberships, channelMemberships, teamsAndChannels, profileImages)
   282  		encoder.Encode(userLine)
   283  		allUsers = append(allUsers, *userLine.User.Username)
   284  	}
   285  
   286  	for team, channels := range teamsAndChannels {
   287  		for _, channel := range channels {
   288  			dates := sortedRandomDates(postsPerChannel)
   289  
   290  			for i := 0; i < postsPerChannel; i++ {
   291  				postLine := createPost(team, channel, allUsers, dates[i])
   292  				encoder.Encode(postLine)
   293  			}
   294  		}
   295  	}
   296  
   297  	for i := 0; i < directChannels; i++ {
   298  		user1 := allUsers[rand.Intn(len(allUsers))]
   299  		user2 := allUsers[rand.Intn(len(allUsers))]
   300  		channelLine := createDirectChannel([]string{user1, user2})
   301  		encoder.Encode(channelLine)
   302  
   303  		dates := sortedRandomDates(postsPerDirectChannel)
   304  		for j := 0; j < postsPerDirectChannel; j++ {
   305  			postLine := createDirectPost([]string{user1, user2}, dates[j])
   306  			encoder.Encode(postLine)
   307  		}
   308  	}
   309  
   310  	for i := 0; i < groupChannels; i++ {
   311  		users := []string{}
   312  		totalUsers := 3 + rand.Intn(3)
   313  		for len(users) < totalUsers {
   314  			user := allUsers[rand.Intn(len(allUsers))]
   315  			if !sliceIncludes(users, user) {
   316  				users = append(users, user)
   317  			}
   318  		}
   319  		channelLine := createDirectChannel(users)
   320  		encoder.Encode(channelLine)
   321  
   322  		dates := sortedRandomDates(postsPerGroupChannel)
   323  		for j := 0; j < postsPerGroupChannel; j++ {
   324  			postLine := createDirectPost(users, dates[j])
   325  			encoder.Encode(postLine)
   326  		}
   327  	}
   328  
   329  	if bulk == "" {
   330  		_, err := bulkFile.Seek(0, 0)
   331  		if err != nil {
   332  			return errors.New("Unable to read correctly the temporary file.")
   333  		}
   334  		importErr, lineNumber := a.BulkImport(bulkFile, false, workers)
   335  		if importErr != nil {
   336  			return fmt.Errorf("%s: %s, %s (line: %d)", importErr.Where, importErr.Message, importErr.DetailedError, lineNumber)
   337  		}
   338  	} else if bulk != "-" {
   339  		err := bulkFile.Close()
   340  		if err != nil {
   341  			return errors.New("Unable to close correctly the output file")
   342  		}
   343  	}
   344  
   345  	return nil
   346  }
   347  
   348  func createUser(idx int, teamMemberships int, channelMemberships int, teamsAndChannels map[string][]string, profileImages []string) app.LineImportData {
   349  	password := fmt.Sprintf("user-%d", idx)
   350  	email := fmt.Sprintf("user-%d@sample.mattermost.com", idx)
   351  	firstName := fake.FirstName()
   352  	lastName := fake.LastName()
   353  	username := fmt.Sprintf("%s.%s", strings.ToLower(firstName), strings.ToLower(lastName))
   354  	if idx == 0 {
   355  		username = "sysadmin"
   356  		password = "sysadmin"
   357  		email = "sysadmin@sample.mattermost.com"
   358  	} else if idx == 1 {
   359  		username = "user-1"
   360  	}
   361  	position := fake.JobTitle()
   362  	roles := "system_user"
   363  	if idx%5 == 0 {
   364  		roles = "system_admin system_user"
   365  	}
   366  
   367  	// The 75% of the users have custom profile image
   368  	var profileImage *string = nil
   369  	if rand.Intn(4) != 0 {
   370  		profileImageSelector := rand.Int()
   371  		if len(profileImages) > 0 {
   372  			profileImage = &profileImages[profileImageSelector%len(profileImages)]
   373  		}
   374  	}
   375  
   376  	useMilitaryTime := "false"
   377  	if idx != 0 && rand.Intn(2) == 0 {
   378  		useMilitaryTime = "true"
   379  	}
   380  
   381  	collapsePreviews := "false"
   382  	if idx != 0 && rand.Intn(2) == 0 {
   383  		collapsePreviews = "true"
   384  	}
   385  
   386  	messageDisplay := "clean"
   387  	if idx != 0 && rand.Intn(2) == 0 {
   388  		messageDisplay = "compact"
   389  	}
   390  
   391  	channelDisplayMode := "full"
   392  	if idx != 0 && rand.Intn(2) == 0 {
   393  		channelDisplayMode = "centered"
   394  	}
   395  
   396  	// Some users has nickname
   397  	nickname := ""
   398  	if rand.Intn(5) == 0 {
   399  		nickname = fake.Company()
   400  	}
   401  
   402  	// sysadmin, user-1 and user-2 users skip tutorial steps
   403  	// Other half of users also skip tutorial steps
   404  	tutorialStep := "999"
   405  	if idx > 2 {
   406  		switch rand.Intn(6) {
   407  		case 1:
   408  			tutorialStep = "1"
   409  		case 2:
   410  			tutorialStep = "2"
   411  		case 3:
   412  			tutorialStep = "3"
   413  		}
   414  	}
   415  
   416  	teams := []app.UserTeamImportData{}
   417  	possibleTeams := []string{}
   418  	for teamName := range teamsAndChannels {
   419  		possibleTeams = append(possibleTeams, teamName)
   420  	}
   421  	sort.Strings(possibleTeams)
   422  	for x := 0; x < teamMemberships; x++ {
   423  		if len(possibleTeams) == 0 {
   424  			break
   425  		}
   426  		position := rand.Intn(len(possibleTeams))
   427  		team := possibleTeams[position]
   428  		possibleTeams = append(possibleTeams[:position], possibleTeams[position+1:]...)
   429  		if teamChannels, err := teamsAndChannels[team]; err {
   430  			teams = append(teams, createTeamMembership(channelMemberships, teamChannels, &team))
   431  		}
   432  	}
   433  
   434  	user := app.UserImportData{
   435  		ProfileImage:       profileImage,
   436  		Username:           &username,
   437  		Email:              &email,
   438  		Password:           &password,
   439  		Nickname:           &nickname,
   440  		FirstName:          &firstName,
   441  		LastName:           &lastName,
   442  		Position:           &position,
   443  		Roles:              &roles,
   444  		Teams:              &teams,
   445  		UseMilitaryTime:    &useMilitaryTime,
   446  		CollapsePreviews:   &collapsePreviews,
   447  		MessageDisplay:     &messageDisplay,
   448  		ChannelDisplayMode: &channelDisplayMode,
   449  		TutorialStep:       &tutorialStep,
   450  	}
   451  	return app.LineImportData{
   452  		Type: "user",
   453  		User: &user,
   454  	}
   455  }
   456  
   457  func createTeamMembership(numOfchannels int, teamChannels []string, teamName *string) app.UserTeamImportData {
   458  	roles := "team_user"
   459  	if rand.Intn(5) == 0 {
   460  		roles = "team_user team_admin"
   461  	}
   462  	channels := []app.UserChannelImportData{}
   463  	teamChannelsCopy := append([]string(nil), teamChannels...)
   464  	for x := 0; x < numOfchannels; x++ {
   465  		if len(teamChannelsCopy) == 0 {
   466  			break
   467  		}
   468  		position := rand.Intn(len(teamChannelsCopy))
   469  		channelName := teamChannelsCopy[position]
   470  		teamChannelsCopy = append(teamChannelsCopy[:position], teamChannelsCopy[position+1:]...)
   471  		channels = append(channels, createChannelMembership(channelName))
   472  	}
   473  
   474  	return app.UserTeamImportData{
   475  		Name:     teamName,
   476  		Roles:    &roles,
   477  		Channels: &channels,
   478  	}
   479  }
   480  
   481  func createChannelMembership(channelName string) app.UserChannelImportData {
   482  	roles := "channel_user"
   483  	if rand.Intn(5) == 0 {
   484  		roles = "channel_user channel_admin"
   485  	}
   486  	favorite := rand.Intn(5) == 0
   487  
   488  	return app.UserChannelImportData{
   489  		Name:     &channelName,
   490  		Roles:    &roles,
   491  		Favorite: &favorite,
   492  	}
   493  }
   494  
   495  func createTeam(idx int) app.LineImportData {
   496  	displayName := fake.Word()
   497  	name := fmt.Sprintf("%s-%d", fake.Word(), idx)
   498  	allowOpenInvite := rand.Intn(2) == 0
   499  
   500  	description := fake.Paragraph()
   501  	if len(description) > 255 {
   502  		description = description[0:255]
   503  	}
   504  
   505  	teamType := "O"
   506  	if rand.Intn(2) == 0 {
   507  		teamType = "I"
   508  	}
   509  
   510  	team := app.TeamImportData{
   511  		DisplayName:     &displayName,
   512  		Name:            &name,
   513  		AllowOpenInvite: &allowOpenInvite,
   514  		Description:     &description,
   515  		Type:            &teamType,
   516  	}
   517  	return app.LineImportData{
   518  		Type: "team",
   519  		Team: &team,
   520  	}
   521  }
   522  
   523  func createChannel(idx int, teamName string) app.LineImportData {
   524  	displayName := fake.Word()
   525  	name := fmt.Sprintf("%s-%d", fake.Word(), idx)
   526  	header := fake.Paragraph()
   527  	purpose := fake.Paragraph()
   528  
   529  	if len(purpose) > 250 {
   530  		purpose = purpose[0:250]
   531  	}
   532  
   533  	channelType := "P"
   534  	if rand.Intn(2) == 0 {
   535  		channelType = "O"
   536  	}
   537  
   538  	channel := app.ChannelImportData{
   539  		Team:        &teamName,
   540  		Name:        &name,
   541  		DisplayName: &displayName,
   542  		Type:        &channelType,
   543  		Header:      &header,
   544  		Purpose:     &purpose,
   545  	}
   546  	return app.LineImportData{
   547  		Type:    "channel",
   548  		Channel: &channel,
   549  	}
   550  }
   551  
   552  func createPost(team string, channel string, allUsers []string, createAt int64) app.LineImportData {
   553  	message := randomMessage(allUsers)
   554  	create_at := createAt
   555  	user := allUsers[rand.Intn(len(allUsers))]
   556  
   557  	// Some messages are flagged by an user
   558  	flagged_by := []string{}
   559  	if rand.Intn(10) == 0 {
   560  		flagged_by = append(flagged_by, allUsers[rand.Intn(len(allUsers))])
   561  	}
   562  
   563  	reactions := []app.ReactionImportData{}
   564  	if rand.Intn(10) == 0 {
   565  		for {
   566  			reactions = append(reactions, randomReaction(allUsers, create_at))
   567  			if rand.Intn(3) == 0 {
   568  				break
   569  			}
   570  		}
   571  	}
   572  
   573  	replies := []app.ReplyImportData{}
   574  	if rand.Intn(10) == 0 {
   575  		for {
   576  			replies = append(replies, randomReply(allUsers, create_at))
   577  			if rand.Intn(4) == 0 {
   578  				break
   579  			}
   580  		}
   581  	}
   582  
   583  	post := app.PostImportData{
   584  		Team:      &team,
   585  		Channel:   &channel,
   586  		User:      &user,
   587  		Message:   &message,
   588  		CreateAt:  &create_at,
   589  		FlaggedBy: &flagged_by,
   590  		Reactions: &reactions,
   591  		Replies:   &replies,
   592  	}
   593  	return app.LineImportData{
   594  		Type: "post",
   595  		Post: &post,
   596  	}
   597  }
   598  
   599  func createDirectChannel(members []string) app.LineImportData {
   600  	header := fake.Sentence()
   601  
   602  	channel := app.DirectChannelImportData{
   603  		Members: &members,
   604  		Header:  &header,
   605  	}
   606  	return app.LineImportData{
   607  		Type:          "direct_channel",
   608  		DirectChannel: &channel,
   609  	}
   610  }
   611  
   612  func createDirectPost(members []string, createAt int64) app.LineImportData {
   613  	message := randomMessage(members)
   614  	create_at := createAt
   615  	user := members[rand.Intn(len(members))]
   616  
   617  	// Some messages are flagged by an user
   618  	flagged_by := []string{}
   619  	if rand.Intn(10) == 0 {
   620  		flagged_by = append(flagged_by, members[rand.Intn(len(members))])
   621  	}
   622  
   623  	reactions := []app.ReactionImportData{}
   624  	if rand.Intn(10) == 0 {
   625  		for {
   626  			reactions = append(reactions, randomReaction(members, create_at))
   627  			if rand.Intn(3) == 0 {
   628  				break
   629  			}
   630  		}
   631  	}
   632  
   633  	replies := []app.ReplyImportData{}
   634  	if rand.Intn(10) == 0 {
   635  		for {
   636  			replies = append(replies, randomReply(members, create_at))
   637  			if rand.Intn(4) == 0 {
   638  				break
   639  			}
   640  		}
   641  	}
   642  
   643  	post := app.DirectPostImportData{
   644  		ChannelMembers: &members,
   645  		User:           &user,
   646  		Message:        &message,
   647  		CreateAt:       &create_at,
   648  		FlaggedBy:      &flagged_by,
   649  		Reactions:      &reactions,
   650  		Replies:        &replies,
   651  	}
   652  	return app.LineImportData{
   653  		Type:       "direct_post",
   654  		DirectPost: &post,
   655  	}
   656  }