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