PLT-5367: Basic post bulk importing. (#5562)

Этот коммит содержится в:
George Goldberg
2017-02-28 23:55:21 +00:00
коммит произвёл enahum
родитель 48d124ec9a
Коммит f7682ad11a
7 изменённых файлов: 731 добавлений и 16 удалений

Просмотреть файл

@@ -8,6 +8,7 @@ import (
"bytes"
"encoding/json"
"io"
"net/http"
"regexp"
"strings"
"unicode/utf8"
@@ -15,7 +16,6 @@ import (
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
"net/http"
)
// Import Data Models
@@ -25,6 +25,7 @@ type LineImportData struct {
Team *TeamImportData `json:"team"`
Channel *ChannelImportData `json:"channel"`
User *UserImportData `json:"user"`
Post *PostImportData `json:"post"`
Version *int `json:"version"`
}
@@ -85,6 +86,15 @@ type UserChannelNotifyPropsImportData struct {
MarkUnread *string `json:"mark_unread"`
}
type PostImportData struct {
Team *string `json:"team"`
Channel *string `json:"channel"`
User *string `json:"user"`
Message *string `json:"message"`
CreateAt *int64 `json:"create_at"`
}
//
// -- Bulk Import Functions --
// These functions import data directly into the database. Security and permission checks are bypassed but validity is
@@ -152,6 +162,12 @@ func ImportLine(line LineImportData, dryRun bool) *model.AppError {
} else {
return ImportUser(line.User, dryRun)
}
case line.Type == "post":
if line.Post == nil {
return model.NewAppError("BulkImport", "app.import.import_line.null_post.error", nil, "", http.StatusBadRequest)
} else {
return ImportPost(line.Post, dryRun)
}
default:
return model.NewLocAppError("BulkImport", "app.import.import_line.unknown_line_type.error", map[string]interface{}{"Type": line.Type}, "")
}
@@ -698,13 +714,112 @@ func validateUserChannelsImportData(data *[]UserChannelImportData) *model.AppErr
return nil
}
func ImportPost(data *PostImportData, dryRun bool) *model.AppError {
if err := validatePostImportData(data); err != nil {
return err
}
// If this is a Dry Run, do not continue any further.
if dryRun {
return nil
}
var team *model.Team
if result := <-Srv.Store.Team().GetByName(*data.Team); result.Err != nil {
return model.NewAppError("BulkImport", "app.import.import_post.team_not_found.error", map[string]interface{}{"TeamName": *data.Team}, "", http.StatusBadRequest)
} else {
team = result.Data.(*model.Team)
}
var channel *model.Channel
if result := <-Srv.Store.Channel().GetByName(team.Id, *data.Channel, false); result.Err != nil {
return model.NewAppError("BulkImport", "app.import.import_post.channel_not_found.error", map[string]interface{}{"ChannelName": *data.Channel}, "", http.StatusBadRequest)
} else {
channel = result.Data.(*model.Channel)
}
var user *model.User
if result := <-Srv.Store.User().GetByUsername(*data.User); result.Err != nil {
return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": *data.User}, "", http.StatusBadRequest)
} else {
user = result.Data.(*model.User)
}
// Check if this post already exists.
var posts []*model.Post
if result := <-Srv.Store.Post().GetPostsCreatedAt(channel.Id, *data.CreateAt); result.Err != nil {
return result.Err
} else {
posts = result.Data.([]*model.Post)
}
var post *model.Post
for _, p := range posts {
if p.Message == *data.Message {
post = p
break
}
}
if post == nil {
post = &model.Post{}
}
post.ChannelId = channel.Id
post.Message = *data.Message
post.UserId = user.Id
post.CreateAt = *data.CreateAt
post.Hashtags, _ = model.ParseHashtags(post.Message)
if post.Id == "" {
if result := <-Srv.Store.Post().Save(post); result.Err != nil {
return result.Err
}
} else {
if result := <-Srv.Store.Post().Overwrite(post); result.Err != nil {
return result.Err
}
}
return nil
}
func validatePostImportData(data *PostImportData) *model.AppError {
if data.Team == nil {
return model.NewAppError("BulkImport", "app.import.validate_post_import_data.team_missing.error", nil, "", http.StatusBadRequest)
}
if data.Channel == nil {
return model.NewAppError("BulkImport", "app.import.validate_post_import_data.channel_missing.error", nil, "", http.StatusBadRequest)
}
if data.User == nil {
return model.NewAppError("BulkImport", "app.import.validate_post_import_data.user_missing.error", nil, "", http.StatusBadRequest)
}
if data.Message == nil {
return model.NewAppError("BulkImport", "app.import.validate_post_import_data.message_missing.error", nil, "", http.StatusBadRequest)
} else if utf8.RuneCountInString(*data.Message) > model.POST_MESSAGE_MAX_RUNES {
return model.NewAppError("BulkImport", "app.import.validate_post_import_data.message_length.error", nil, "", http.StatusBadRequest)
}
if data.CreateAt == nil {
return model.NewAppError("BulkImport", "app.import.validate_post_import_data.create_at_missing.error", nil, "", http.StatusBadRequest)
} else if *data.CreateAt == 0 {
return model.NewAppError("BulkImport", "app.import.validate_post_import_data.create_at_zero.error", nil, "", http.StatusBadRequest)
}
return nil
}
//
// -- Old SlackImport Functions --
// Import functions are sutible for entering posts and users into the database without
// some of the usual checks. (IsValid is still run)
//
func ImportPost(post *model.Post) {
func OldImportPost(post *model.Post) {
// Workaround for empty messages, which may be the case if they are webhook posts.
firstIteration := true
for messageRuneCount := utf8.RuneCountInString(post.Message); messageRuneCount > 0 || firstIteration; messageRuneCount = utf8.RuneCountInString(post.Message) {
@@ -768,7 +883,7 @@ func OldImportChannel(channel *model.Channel) *model.Channel {
}
}
func ImportFile(file io.Reader, teamId string, channelId string, userId string, fileName string) (*model.FileInfo, error) {
func OldImportFile(file io.Reader, teamId string, channelId string, userId string, fileName string) (*model.FileInfo, error) {
buf := bytes.NewBuffer(nil)
io.Copy(buf, file)
data := buf.Bytes()
@@ -787,7 +902,7 @@ func ImportFile(file io.Reader, teamId string, channelId string, userId string,
return fileInfo, nil
}
func ImportIncomingWebhookPost(post *model.Post, props model.StringInterface) {
func OldImportIncomingWebhookPost(post *model.Post, props model.StringInterface) {
linkWithTextRegex := regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`)
post.Message = linkWithTextRegex.ReplaceAllString(post.Message, "[${2}](${1})")
@@ -838,5 +953,5 @@ func ImportIncomingWebhookPost(post *model.Post, props model.StringInterface) {
}
}
ImportPost(post)
OldImportPost(post)
}

Просмотреть файл

@@ -6,6 +6,7 @@ package app
import (
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
"runtime/debug"
"strings"
"testing"
)
@@ -454,6 +455,108 @@ func TestImportValidateUserChannelsImportData(t *testing.T) {
}
}
func TestImportValidatePostImportData(t *testing.T) {
// Test with minimum required valid properties.
data := PostImportData{
Team: ptrStr("teamname"),
Channel: ptrStr("channelname"),
User: ptrStr("username"),
Message: ptrStr("message"),
CreateAt: ptrInt64(model.GetMillis()),
}
if err := validatePostImportData(&data); err != nil {
t.Fatal("Validation failed but should have been valid.")
}
// Test with missing required properties.
data = PostImportData{
Channel: ptrStr("channelname"),
User: ptrStr("username"),
Message: ptrStr("message"),
CreateAt: ptrInt64(model.GetMillis()),
}
if err := validatePostImportData(&data); err == nil {
t.Fatal("Should have failed due to missing required property.")
}
data = PostImportData{
Team: ptrStr("teamname"),
User: ptrStr("username"),
Message: ptrStr("message"),
CreateAt: ptrInt64(model.GetMillis()),
}
if err := validatePostImportData(&data); err == nil {
t.Fatal("Should have failed due to missing required property.")
}
data = PostImportData{
Team: ptrStr("teamname"),
Channel: ptrStr("channelname"),
Message: ptrStr("message"),
CreateAt: ptrInt64(model.GetMillis()),
}
if err := validatePostImportData(&data); err == nil {
t.Fatal("Should have failed due to missing required property.")
}
data = PostImportData{
Team: ptrStr("teamname"),
Channel: ptrStr("channelname"),
User: ptrStr("username"),
CreateAt: ptrInt64(model.GetMillis()),
}
if err := validatePostImportData(&data); err == nil {
t.Fatal("Should have failed due to missing required property.")
}
data = PostImportData{
Team: ptrStr("teamname"),
Channel: ptrStr("channelname"),
User: ptrStr("username"),
Message: ptrStr("message"),
}
if err := validatePostImportData(&data); err == nil {
t.Fatal("Should have failed due to missing required property.")
}
// Test with invalid message.
data = PostImportData{
Team: ptrStr("teamname"),
Channel: ptrStr("channelname"),
User: ptrStr("username"),
Message: ptrStr(strings.Repeat("1234567890", 500)),
CreateAt: ptrInt64(model.GetMillis()),
}
if err := validatePostImportData(&data); err == nil {
t.Fatal("Should have failed due to too long message.")
}
// Test with invalid CreateAt
data = PostImportData{
Team: ptrStr("teamname"),
Channel: ptrStr("channelname"),
User: ptrStr("username"),
Message: ptrStr("message"),
CreateAt: ptrInt64(0),
}
if err := validatePostImportData(&data); err == nil {
t.Fatal("Should have failed due to 0 create-at value.")
}
// Test with valid all optional parameters.
data = PostImportData{
Team: ptrStr("teamname"),
Channel: ptrStr("channelname"),
User: ptrStr("username"),
Message: ptrStr("message"),
CreateAt: ptrInt64(model.GetMillis()),
}
if err := validatePostImportData(&data); err != nil {
t.Fatal("Should have succeeded.")
}
}
func TestImportImportTeam(t *testing.T) {
_ = Setup()
@@ -1333,6 +1436,252 @@ func TestImportImportUser(t *testing.T) {
}
}
func AssertAllPostsCount(t *testing.T, initialCount int64, change int64, teamName string) {
if result := <-Srv.Store.Post().AnalyticsPostCount(teamName, false, false); result.Err != nil {
t.Fatal(result.Err)
} else {
if initialCount+change != result.Data.(int64) {
debug.PrintStack()
t.Fatalf("Did not find the expected number of posts.")
}
}
}
func TestImportImportPost(t *testing.T) {
_ = Setup()
// Create a Team.
teamName := model.NewId()
ImportTeam(&TeamImportData{
Name: &teamName,
DisplayName: ptrStr("Display Name"),
Type: ptrStr("O"),
}, false)
team, err := GetTeamByName(teamName)
if err != nil {
t.Fatalf("Failed to get team from database.")
}
// Create a Channel.
channelName := model.NewId()
ImportChannel(&ChannelImportData{
Team: &teamName,
Name: &channelName,
DisplayName: ptrStr("Display Name"),
Type: ptrStr("O"),
}, false)
channel, err := GetChannelByName(channelName, team.Id)
if err != nil {
t.Fatalf("Failed to get channel from database.")
}
// Create a user.
username := model.NewId()
ImportUser(&UserImportData{
Username: &username,
Email: ptrStr(model.NewId() + "@example.com"),
}, false)
user, err := GetUserByUsername(username)
if err != nil {
t.Fatalf("Failed to get user from database.")
}
// Count the number of posts in the testing team.
var initialPostCount int64
if result := <-Srv.Store.Post().AnalyticsPostCount(team.Id, false, false); result.Err != nil {
t.Fatal(result.Err)
} else {
initialPostCount = result.Data.(int64)
}
// Try adding an invalid post in dry run mode.
data := &PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
}
if err := ImportPost(data, true); err == nil {
t.Fatalf("Expected error.")
}
AssertAllPostsCount(t, initialPostCount, 0, team.Id)
// Try adding a valid post in dry run mode.
data = &PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: ptrStr("Hello"),
CreateAt: ptrInt64(model.GetMillis()),
}
if err := ImportPost(data, true); err != nil {
t.Fatalf("Expected success.")
}
AssertAllPostsCount(t, initialPostCount, 0, team.Id)
// Try adding an invalid post in apply mode.
data = &PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
CreateAt: ptrInt64(model.GetMillis()),
}
if err := ImportPost(data, false); err == nil {
t.Fatalf("Expected error.")
}
AssertAllPostsCount(t, initialPostCount, 0, team.Id)
// Try adding a valid post with invalid team in apply mode.
data = &PostImportData{
Team: ptrStr(model.NewId()),
Channel: &channelName,
User: &username,
Message: ptrStr("Message"),
CreateAt: ptrInt64(model.GetMillis()),
}
if err := ImportPost(data, false); err == nil {
t.Fatalf("Expected error.")
}
AssertAllPostsCount(t, initialPostCount, 0, team.Id)
// Try adding a valid post with invalid channel in apply mode.
data = &PostImportData{
Team: &teamName,
Channel: ptrStr(model.NewId()),
User: &username,
Message: ptrStr("Message"),
CreateAt: ptrInt64(model.GetMillis()),
}
if err := ImportPost(data, false); err == nil {
t.Fatalf("Expected error.")
}
AssertAllPostsCount(t, initialPostCount, 0, team.Id)
// Try adding a valid post with invalid user in apply mode.
data = &PostImportData{
Team: &teamName,
Channel: &channelName,
User: ptrStr(model.NewId()),
Message: ptrStr("Message"),
CreateAt: ptrInt64(model.GetMillis()),
}
if err := ImportPost(data, false); err == nil {
t.Fatalf("Expected error.")
}
AssertAllPostsCount(t, initialPostCount, 0, team.Id)
// Try adding a valid post in apply mode.
time := model.GetMillis()
data = &PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: ptrStr("Message"),
CreateAt: &time,
}
if err := ImportPost(data, false); err != nil {
t.Fatalf("Expected success.")
}
AssertAllPostsCount(t, initialPostCount, 1, team.Id)
// Check the post values.
if result := <-Srv.Store.Post().GetPostsCreatedAt(channel.Id, time); result.Err != nil {
t.Fatal(result.Err.Error())
} else {
posts := result.Data.([]*model.Post)
if len(posts) != 1 {
t.Fatal("Unexpected number of posts found.")
}
post := posts[0]
if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != user.Id {
t.Fatal("Post properties not as expected")
}
}
// Update the post.
data = &PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: ptrStr("Message"),
CreateAt: &time,
}
if err := ImportPost(data, false); err != nil {
t.Fatalf("Expected success.")
}
AssertAllPostsCount(t, initialPostCount, 1, team.Id)
// Check the post values.
if result := <-Srv.Store.Post().GetPostsCreatedAt(channel.Id, time); result.Err != nil {
t.Fatal(result.Err.Error())
} else {
posts := result.Data.([]*model.Post)
if len(posts) != 1 {
t.Fatal("Unexpected number of posts found.")
}
post := posts[0]
if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != user.Id {
t.Fatal("Post properties not as expected")
}
}
// Save the post with a different time.
newTime := time + 1
data = &PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: ptrStr("Message"),
CreateAt: &newTime,
}
if err := ImportPost(data, false); err != nil {
t.Fatalf("Expected success.")
}
AssertAllPostsCount(t, initialPostCount, 2, team.Id)
// Save the post with a different message.
data = &PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: ptrStr("Message 2"),
CreateAt: &time,
}
if err := ImportPost(data, false); err != nil {
t.Fatalf("Expected success.")
}
AssertAllPostsCount(t, initialPostCount, 3, team.Id)
// Test with hashtags
hashtagTime := time + 2
data = &PostImportData{
Team: &teamName,
Channel: &channelName,
User: &username,
Message: ptrStr("Message 2 #hashtagmashupcity"),
CreateAt: &hashtagTime,
}
if err := ImportPost(data, false); err != nil {
t.Fatalf("Expected success.")
}
AssertAllPostsCount(t, initialPostCount, 4, team.Id)
if result := <-Srv.Store.Post().GetPostsCreatedAt(channel.Id, hashtagTime); result.Err != nil {
t.Fatal(result.Err.Error())
} else {
posts := result.Data.([]*model.Post)
if len(posts) != 1 {
t.Fatal("Unexpected number of posts found.")
}
post := posts[0]
if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != user.Id {
t.Fatal("Post properties not as expected")
}
if post.Hashtags != "#hashtagmashupcity" {
t.Fatalf("Hashtags not as expected: %s", post.Hashtags)
}
}
}
func TestImportImportLine(t *testing.T) {
_ = Setup()
@@ -1362,6 +1711,12 @@ func TestImportImportLine(t *testing.T) {
if err := ImportLine(line, false); err == nil {
t.Fatalf("Expected an error when importing a line with type uesr with a nil user.")
}
// Try import line with post type but nil post.
line.Type = "post"
if err := ImportLine(line, false); err == nil {
t.Fatalf("Expected an error when importing a line with type post with a nil post.")
}
}
func TestImportBulkImport(t *testing.T) {
@@ -1369,13 +1724,14 @@ func TestImportBulkImport(t *testing.T) {
teamName := model.NewId()
channelName := model.NewId()
username := model.NewId()
// Run bulk import with a valid 1 of everything.
data1 := `{"type": "version", "version": 1}
{"type": "team", "team": {"type": "O", "display_name": "lskmw2d7a5ao7ppwqh5ljchvr4", "name": "` + teamName + `"}}
{"type": "channel", "channel": {"type": "O", "display_name": "xr6m6udffngark2uekvr3hoeny", "team": "` + teamName + `", "name": "` + channelName + `"}}
{"type": "user", "user": {"username": "kufjgnkxkrhhfgbrip6qxkfsaa", "email": "kufjgnkxkrhhfgbrip6qxkfsaa@example.com"}}
{"type": "user", "user": {"username": "bwshaim6qnc2ne7oqkd5b2s2rq", "email": "bwshaim6qnc2ne7oqkd5b2s2rq@example.com", "teams": [{"name": "` + teamName + `", "channels": [{"name": "` + channelName + `"}]}]}}`
{"type": "user", "user": {"username": "` + username + `", "email": "` + username + `@example.com", "teams": [{"name": "` + teamName + `", "channels": [{"name": "` + channelName + `"}]}]}}
{"type": "post", "post": {"team": "` + teamName + `", "channel": "` + channelName + `", "user": "` + username + `", "message": "Hello World", "create_at": 123456789012}}`
if err, line := BulkImport(strings.NewReader(data1), false); err != nil || line != 0 {
t.Fatalf("BulkImport should have succeeded: %v, %v", err.Error(), line)

Просмотреть файл

@@ -242,7 +242,7 @@ func SlackAddPosts(teamId string, channel *model.Channel, posts []SlackPost, use
newPost.Message = sPost.File.Title
}
}
ImportPost(&newPost)
OldImportPost(&newPost)
for _, fileId := range newPost.FileIds {
if result := <-Srv.Store.FileInfo().AttachToPost(fileId, newPost.Id); result.Err != nil {
l4g.Error(utils.T("api.slackimport.slack_add_posts.attach_files.error"), newPost.Id, newPost.FileIds, result.Err)
@@ -266,7 +266,7 @@ func SlackAddPosts(teamId string, channel *model.Channel, posts []SlackPost, use
Message: sPost.Comment.Comment,
CreateAt: SlackConvertTimeStamp(sPost.TimeStamp),
}
ImportPost(&newPost)
OldImportPost(&newPost)
case sPost.Type == "message" && sPost.SubType == "bot_message":
if botUser == nil {
l4g.Warn(utils.T("api.slackimport.slack_add_posts.bot_user_no_exists.warn"))
@@ -298,7 +298,7 @@ func SlackAddPosts(teamId string, channel *model.Channel, posts []SlackPost, use
Type: model.POST_SLACK_ATTACHMENT,
}
ImportIncomingWebhookPost(post, props)
OldImportIncomingWebhookPost(post, props)
case sPost.Type == "message" && (sPost.SubType == "channel_join" || sPost.SubType == "channel_leave"):
if sPost.User == "" {
l4g.Debug(utils.T("api.slackimport.slack_add_posts.msg_no_usr.debug"))
@@ -325,7 +325,7 @@ func SlackAddPosts(teamId string, channel *model.Channel, posts []SlackPost, use
"username": users[sPost.User].Username,
},
}
ImportPost(&newPost)
OldImportPost(&newPost)
case sPost.Type == "message" && sPost.SubType == "me_message":
if sPost.User == "" {
l4g.Debug(utils.T("api.slackimport.slack_add_posts.without_user.debug"))
@@ -340,7 +340,7 @@ func SlackAddPosts(teamId string, channel *model.Channel, posts []SlackPost, use
Message: "*" + sPost.Text + "*",
CreateAt: SlackConvertTimeStamp(sPost.TimeStamp),
}
ImportPost(&newPost)
OldImportPost(&newPost)
case sPost.Type == "message" && sPost.SubType == "channel_topic":
if sPost.User == "" {
l4g.Debug(utils.T("api.slackimport.slack_add_posts.msg_no_usr.debug"))
@@ -356,7 +356,7 @@ func SlackAddPosts(teamId string, channel *model.Channel, posts []SlackPost, use
CreateAt: SlackConvertTimeStamp(sPost.TimeStamp),
Type: model.POST_HEADER_CHANGE,
}
ImportPost(&newPost)
OldImportPost(&newPost)
case sPost.Type == "message" && sPost.SubType == "channel_purpose":
if sPost.User == "" {
l4g.Debug(utils.T("api.slackimport.slack_add_posts.msg_no_usr.debug"))
@@ -372,7 +372,7 @@ func SlackAddPosts(teamId string, channel *model.Channel, posts []SlackPost, use
CreateAt: SlackConvertTimeStamp(sPost.TimeStamp),
Type: model.POST_PURPOSE_CHANGE,
}
ImportPost(&newPost)
OldImportPost(&newPost)
case sPost.Type == "message" && sPost.SubType == "channel_name":
if sPost.User == "" {
l4g.Debug(utils.T("api.slackimport.slack_add_posts.msg_no_usr.debug"))
@@ -388,7 +388,7 @@ func SlackAddPosts(teamId string, channel *model.Channel, posts []SlackPost, use
CreateAt: SlackConvertTimeStamp(sPost.TimeStamp),
Type: model.POST_DISPLAYNAME_CHANGE,
}
ImportPost(&newPost)
OldImportPost(&newPost)
default:
l4g.Warn(utils.T("api.slackimport.slack_add_posts.unsupported.warn"), sPost.Type, sPost.SubType)
}
@@ -405,7 +405,7 @@ func SlackUploadFile(sPost SlackPost, uploads map[string]*zip.File, teamId strin
}
defer openFile.Close()
uploadedFile, err := ImportFile(openFile, teamId, channelId, userId, filepath.Base(file.Name))
uploadedFile, err := OldImportFile(openFile, teamId, channelId, userId, filepath.Base(file.Name))
if err != nil {
l4g.Warn(utils.T("api.slackimport.slack_add_posts.upload_file_upload_failed.warn", map[string]interface{}{"FileId": sPost.File.Id, "Error": err.Error()}))
return nil, false

Просмотреть файл

@@ -2983,6 +2983,50 @@
"id": "app.import.validate_user_teams_import_data.team_name_missing.error",
"translation": "Team name missing from User's Team Membership."
},
{
"id": "app.import.import_post.team_not_found.error",
"translation": "Error importing post. Team with name \"{{.TeamName}}\" could not be found."
},
{
"id": "app.import.import_post.channel_not_found.error",
"translation": "Error importing post. Channel with name \"{{.ChannelName}}\" could not be found."
},
{
"id": "app.import.import_post.user_not_found.error",
"translation": "Error importing post. User with username \"{{.Username}}\" could not be found."
},
{
"id": "app.import.validate_post_import_data.team_missing.error",
"translation": "Missing required Post property: Team."
},
{
"id": "app.import.validate_post_import_data.channel_missing.error",
"translation": "Missing required Post property: Channel."
},
{
"id": "app.import.validate_post_import_data.user_missing.error",
"translation": "Missing required Post property: User."
},
{
"id": "app.import.validate_post_import_data.message_missing.error",
"translation": "Missing required Post property: Message."
},
{
"id": "app.import.validate_post_import_data.create_at_missing.error",
"translation": "Missing required Post property: create_at."
},
{
"id": "app.import.validate_post_import_data.message_length.error",
"translation": "Post Message property is longer than the maximum permitted length."
},
{
"id": "app.import.validate_post_import_data.create_at_zero.error",
"translation": "Post CreateAt must not be zero if it is provided."
},
{
"id": "app.import.import_line.null_post.error",
"translation": "Import data line has type \"post\" but the post object is null."
},
{
"id": "authentication.permissions.create_team_roles.description",
"translation": "Ability to create new teams"
@@ -4943,6 +4987,10 @@
"id": "store.sql_post.get_posts_around.get.app_error",
"translation": "We couldn't get the posts for the channel"
},
{
"id": "store.sql_post.get_posts_created_att.app_error",
"translation": "We couldn't get the posts for the channel"
},
{
"id": "store.sql_post.get_posts_around.get_parent.app_error",
"translation": "We couldn't get the parent posts for the channel"
@@ -4991,6 +5039,10 @@
"id": "store.sql_post.update.app_error",
"translation": "We couldn't update the Post"
},
{
"id": "store.sql_post.overwrite.app_error",
"translation": "We couldn't overwrite the Post"
},
{
"id": "store.sql_preference.delete.app_error",
"translation": "We encountered an error while deleting preferences"

Просмотреть файл

@@ -12,6 +12,7 @@ import (
"github.com/mattermost/platform/einterfaces"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
"net/http"
)
type SqlPostStore struct {
@@ -157,6 +158,33 @@ func (s SqlPostStore) Update(newPost *model.Post, oldPost *model.Post) StoreChan
return storeChannel
}
func (s SqlPostStore) Overwrite(post *model.Post) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
post.UpdateAt = model.GetMillis()
if result.Err = post.IsValid(); result.Err != nil {
storeChannel <- result
close(storeChannel)
return
}
if _, err := s.GetMaster().Update(post); err != nil {
result.Err = model.NewLocAppError("SqlPostStore.Overwrite", "store.sql_post.overwrite.app_error", nil, "id="+post.Id+", "+err.Error())
} else {
result.Data = post
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlPostStore) GetFlaggedPosts(userId string, offset int, limit int) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
@@ -1132,3 +1160,27 @@ func (s SqlPostStore) AnalyticsPostCount(teamId string, mustHaveFile bool, mustH
return storeChannel
}
func (s SqlPostStore) GetPostsCreatedAt(channelId string, time int64) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
query := `SELECT * FROM Posts WHERE CreateAt = :CreateAt`
var posts []*model.Post
_, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"CreateAt": time})
if err != nil {
result.Err = model.NewAppError("SqlPostStore.GetPostsCreatedAt", "store.sql_post.get_posts_created_att.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError)
} else {
result.Data = posts
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}

Просмотреть файл

@@ -1127,3 +1127,141 @@ func TestPostStoreGetFlaggedPosts(t *testing.T) {
t.Fatal("should have 2 posts")
}
}
func TestPostStoreGetPostsCreatedAt(t *testing.T) {
Setup()
createTime := model.GetMillis()
o0 := &model.Post{}
o0.ChannelId = model.NewId()
o0.UserId = model.NewId()
o0.Message = "a" + model.NewId() + "b"
o0.CreateAt = createTime
o0 = (<-store.Post().Save(o0)).Data.(*model.Post)
o1 := &model.Post{}
o1.ChannelId = o0.Id
o1.UserId = model.NewId()
o1.Message = "a" + model.NewId() + "b"
o0.CreateAt = createTime
o1 = (<-store.Post().Save(o1)).Data.(*model.Post)
time.Sleep(2 * time.Millisecond)
o2 := &model.Post{}
o2.ChannelId = o1.ChannelId
o2.UserId = model.NewId()
o2.Message = "a" + model.NewId() + "b"
o2.ParentId = o1.Id
o2.RootId = o1.Id
o2 = (<-store.Post().Save(o2)).Data.(*model.Post)
time.Sleep(2 * time.Millisecond)
o3 := &model.Post{}
o3.ChannelId = model.NewId()
o3.UserId = model.NewId()
o3.Message = "a" + model.NewId() + "b"
o3.CreateAt = createTime
o3 = (<-store.Post().Save(o3)).Data.(*model.Post)
time.Sleep(2 * time.Millisecond)
r1 := (<-store.Post().GetPostsCreatedAt(o1.ChannelId, createTime)).Data.([]*model.Post)
if len(r1) != 2 {
t.Fatalf("Got the wrong number of posts.")
}
}
func TestPostStoreOverwrite(t *testing.T) {
Setup()
o1 := &model.Post{}
o1.ChannelId = model.NewId()
o1.UserId = model.NewId()
o1.Message = "a" + model.NewId() + "AAAAAAAAAAA"
o1 = (<-store.Post().Save(o1)).Data.(*model.Post)
o2 := &model.Post{}
o2.ChannelId = o1.ChannelId
o2.UserId = model.NewId()
o2.Message = "a" + model.NewId() + "CCCCCCCCC"
o2.ParentId = o1.Id
o2.RootId = o1.Id
o2 = (<-store.Post().Save(o2)).Data.(*model.Post)
o3 := &model.Post{}
o3.ChannelId = o1.ChannelId
o3.UserId = model.NewId()
o3.Message = "a" + model.NewId() + "QQQQQQQQQQ"
o3 = (<-store.Post().Save(o3)).Data.(*model.Post)
ro1 := (<-store.Post().Get(o1.Id)).Data.(*model.PostList).Posts[o1.Id]
ro2 := (<-store.Post().Get(o1.Id)).Data.(*model.PostList).Posts[o2.Id]
ro3 := (<-store.Post().Get(o3.Id)).Data.(*model.PostList).Posts[o3.Id]
if ro1.Message != o1.Message {
t.Fatal("Failed to save/get")
}
o1a := &model.Post{}
*o1a = *ro1
o1a.Message = ro1.Message + "BBBBBBBBBB"
if result := <-store.Post().Overwrite(o1a); result.Err != nil {
t.Fatal(result.Err)
}
ro1a := (<-store.Post().Get(o1.Id)).Data.(*model.PostList).Posts[o1.Id]
if ro1a.Message != o1a.Message {
t.Fatal("Failed to overwrite/get")
}
o2a := &model.Post{}
*o2a = *ro2
o2a.Message = ro2.Message + "DDDDDDD"
if result := <-store.Post().Overwrite(o2a); result.Err != nil {
t.Fatal(result.Err)
}
ro2a := (<-store.Post().Get(o1.Id)).Data.(*model.PostList).Posts[o2.Id]
if ro2a.Message != o2a.Message {
t.Fatal("Failed to overwrite/get")
}
o3a := &model.Post{}
*o3a = *ro3
o3a.Message = ro3.Message + "WWWWWWW"
if result := <-store.Post().Overwrite(o3a); result.Err != nil {
t.Fatal(result.Err)
}
ro3a := (<-store.Post().Get(o3.Id)).Data.(*model.PostList).Posts[o3.Id]
if ro3a.Message != o3a.Message && ro3a.Hashtags != o3a.Hashtags {
t.Fatal("Failed to overwrite/get")
}
o4 := Must(store.Post().Save(&model.Post{
ChannelId: model.NewId(),
UserId: model.NewId(),
Message: model.NewId(),
Filenames: []string{"test"},
})).(*model.Post)
ro4 := (<-store.Post().Get(o4.Id)).Data.(*model.PostList).Posts[o4.Id]
o4a := &model.Post{}
*o4a = *ro4
o4a.Filenames = []string{}
o4a.FileIds = []string{model.NewId()}
if result := <-store.Post().Overwrite(o4a); result.Err != nil {
t.Fatal(result.Err)
}
if ro4a := Must(store.Post().Get(o4.Id)).(*model.PostList).Posts[o4.Id]; len(ro4a.Filenames) != 0 {
t.Fatal("Failed to clear Filenames")
} else if len(ro4a.FileIds) != 1 {
t.Fatal("Failed to set FileIds")
}
}

Просмотреть файл

@@ -152,6 +152,8 @@ type PostStore interface {
AnalyticsPostCountsByDay(teamId string) StoreChannel
AnalyticsPostCount(teamId string, mustHaveFile bool, mustHaveHashtag bool) StoreChannel
InvalidateLastPostTimeCache(channelId string)
GetPostsCreatedAt(channelId string, time int64) StoreChannel
Overwrite(post *model.Post) StoreChannel
}
type UserStore interface {