diff options
Diffstat (limited to 'model')
-rw-r--r-- | model/analytics_row.go | 55 | ||||
-rw-r--r-- | model/analytics_row_test.go | 37 | ||||
-rw-r--r-- | model/channel.go | 11 | ||||
-rw-r--r-- | model/channel_test.go | 20 | ||||
-rw-r--r-- | model/client.go | 26 | ||||
-rw-r--r-- | model/command.go | 3 | ||||
-rw-r--r-- | model/config.go | 6 | ||||
-rw-r--r-- | model/incoming_webhook.go | 18 | ||||
-rw-r--r-- | model/outgoing_webhook.go | 6 | ||||
-rw-r--r-- | model/outgoing_webhook_test.go | 5 | ||||
-rw-r--r-- | model/post_list.go | 9 | ||||
-rw-r--r-- | model/post_list_test.go | 34 | ||||
-rw-r--r-- | model/search_params.go | 88 | ||||
-rw-r--r-- | model/search_params_test.go | 17 | ||||
-rw-r--r-- | model/team.go | 30 | ||||
-rw-r--r-- | model/utils.go | 13 |
16 files changed, 314 insertions, 64 deletions
diff --git a/model/analytics_row.go b/model/analytics_row.go new file mode 100644 index 000000000..ed1d69dd2 --- /dev/null +++ b/model/analytics_row.go @@ -0,0 +1,55 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type AnalyticsRow struct { + Name string `json:"name"` + Value float64 `json:"value"` +} + +type AnalyticsRows []*AnalyticsRow + +func (me *AnalyticsRow) ToJson() string { + b, err := json.Marshal(me) + if err != nil { + return "" + } else { + return string(b) + } +} + +func AnalyticsRowFromJson(data io.Reader) *AnalyticsRow { + decoder := json.NewDecoder(data) + var me AnalyticsRow + err := decoder.Decode(&me) + if err == nil { + return &me + } else { + return nil + } +} + +func (me AnalyticsRows) ToJson() string { + if b, err := json.Marshal(me); err != nil { + return "[]" + } else { + return string(b) + } +} + +func AnalyticsRowsFromJson(data io.Reader) AnalyticsRows { + decoder := json.NewDecoder(data) + var me AnalyticsRows + err := decoder.Decode(&me) + if err == nil { + return me + } else { + return nil + } +} diff --git a/model/analytics_row_test.go b/model/analytics_row_test.go new file mode 100644 index 000000000..1202d5b52 --- /dev/null +++ b/model/analytics_row_test.go @@ -0,0 +1,37 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "strings" + "testing" +) + +func TestAnalyticsRowJson(t *testing.T) { + a1 := AnalyticsRow{} + a1.Name = "2015-10-12" + a1.Value = 12345.0 + json := a1.ToJson() + ra1 := AnalyticsRowFromJson(strings.NewReader(json)) + + if a1.Name != ra1.Name { + t.Fatal("days didn't match") + } +} + +func TestAnalyticsRowsJson(t *testing.T) { + a1 := AnalyticsRow{} + a1.Name = "2015-10-12" + a1.Value = 12345.0 + + var a1s AnalyticsRows = make([]*AnalyticsRow, 1) + a1s[0] = &a1 + + ljson := a1s.ToJson() + results := AnalyticsRowsFromJson(strings.NewReader(ljson)) + + if a1s[0].Name != results[0].Name { + t.Fatal("Ids do not match") + } +} diff --git a/model/channel.go b/model/channel.go index 076ddf0b5..ac54a7e44 100644 --- a/model/channel.go +++ b/model/channel.go @@ -24,7 +24,8 @@ type Channel struct { Type string `json:"type"` DisplayName string `json:"display_name"` Name string `json:"name"` - Description string `json:"description"` + Header string `json:"header"` + Purpose string `json:"purpose"` LastPostAt int64 `json:"last_post_at"` TotalMsgCount int64 `json:"total_msg_count"` ExtraUpdateAt int64 `json:"extra_update_at"` @@ -89,8 +90,12 @@ func (o *Channel) IsValid() *AppError { return NewAppError("Channel.IsValid", "Invalid type", "id="+o.Id) } - if len(o.Description) > 1024 { - return NewAppError("Channel.IsValid", "Invalid description", "id="+o.Id) + if len(o.Header) > 1024 { + return NewAppError("Channel.IsValid", "Invalid header", "id="+o.Id) + } + + if len(o.Purpose) > 128 { + return NewAppError("Channel.IsValid", "Invalid purpose", "id="+o.Id) } if len(o.CreatorId) > 26 { diff --git a/model/channel_test.go b/model/channel_test.go index e5dfa3734..590417cfe 100644 --- a/model/channel_test.go +++ b/model/channel_test.go @@ -67,6 +67,26 @@ func TestChannelIsValid(t *testing.T) { if err := o.IsValid(); err != nil { t.Fatal(err) } + + o.Header = strings.Repeat("01234567890", 100) + if err := o.IsValid(); err == nil { + t.Fatal("should be invalid") + } + + o.Header = "1234" + if err := o.IsValid(); err != nil { + t.Fatal(err) + } + + o.Purpose = strings.Repeat("01234567890", 20) + if err := o.IsValid(); err == nil { + t.Fatal("should be invalid") + } + + o.Purpose = "1234" + if err := o.IsValid(); err != nil { + t.Fatal(err) + } } func TestChannelPreSave(t *testing.T) { diff --git a/model/client.go b/model/client.go index 48a560838..19183098e 100644 --- a/model/client.go +++ b/model/client.go @@ -211,8 +211,8 @@ func (c *Client) InviteMembers(invites *Invites) (*Result, *AppError) { } } -func (c *Client) UpdateTeamDisplayName(data map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost("/teams/update_name", MapToJson(data)); err != nil { +func (c *Client) UpdateTeam(team *Team) (*Result, *AppError) { + if r, err := c.DoApiPost("/teams/update", team.ToJson()); err != nil { return nil, err } else { return &Result{r.Header.Get(HEADER_REQUEST_ID), @@ -416,6 +416,15 @@ func (c *Client) TestEmail(config *Config) (*Result, *AppError) { } } +func (c *Client) GetAnalytics(teamId, name string) (*Result, *AppError) { + if r, err := c.DoApiGet("/admin/analytics/"+teamId+"/"+name, "", ""); err != nil { + return nil, err + } else { + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), AnalyticsRowsFromJson(r.Body)}, nil + } +} + func (c *Client) CreateChannel(channel *Channel) (*Result, *AppError) { if r, err := c.DoApiPost("/channels/create", channel.ToJson()); err != nil { return nil, err @@ -443,8 +452,17 @@ func (c *Client) UpdateChannel(channel *Channel) (*Result, *AppError) { } } -func (c *Client) UpdateChannelDesc(data map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost("/channels/update_desc", MapToJson(data)); err != nil { +func (c *Client) UpdateChannelHeader(data map[string]string) (*Result, *AppError) { + if r, err := c.DoApiPost("/channels/update_header", MapToJson(data)); err != nil { + return nil, err + } else { + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil + } +} + +func (c *Client) UpdateChannelPurpose(data map[string]string) (*Result, *AppError) { + if r, err := c.DoApiPost("/channels/update_purpose", MapToJson(data)); err != nil { return nil, err } else { return &Result{r.Header.Get(HEADER_REQUEST_ID), diff --git a/model/command.go b/model/command.go index 2b26aad1c..5aec5f534 100644 --- a/model/command.go +++ b/model/command.go @@ -9,7 +9,8 @@ import ( ) const ( - RESP_EXECUTED = "executed" + RESP_EXECUTED = "executed" + RESP_NOT_IMPLEMENTED = "not implemented" ) type Command struct { diff --git a/model/config.go b/model/config.go index 216b1de86..50a8dc133 100644 --- a/model/config.go +++ b/model/config.go @@ -123,6 +123,7 @@ type TeamSettings struct { EnableUserCreation bool RestrictCreationToDomains string RestrictTeamNames *bool + EnableTeamListing *bool } type Config struct { @@ -175,6 +176,11 @@ func (o *Config) SetDefaults() { o.TeamSettings.RestrictTeamNames = new(bool) *o.TeamSettings.RestrictTeamNames = true } + + if o.TeamSettings.EnableTeamListing == nil { + o.TeamSettings.EnableTeamListing = new(bool) + *o.TeamSettings.EnableTeamListing = false + } } func (o *Config) IsValid() *AppError { diff --git a/model/incoming_webhook.go b/model/incoming_webhook.go index 9b9969b96..be1984244 100644 --- a/model/incoming_webhook.go +++ b/model/incoming_webhook.go @@ -23,6 +23,13 @@ type IncomingWebhook struct { TeamId string `json:"team_id"` } +type IncomingWebhookRequest struct { + Text string `json:"text"` + Username string `json:"username"` + IconURL string `json:"icon_url"` + ChannelName string `json:"channel"` +} + func (o *IncomingWebhook) ToJson() string { b, err := json.Marshal(o) if err != nil { @@ -104,3 +111,14 @@ func (o *IncomingWebhook) PreSave() { func (o *IncomingWebhook) PreUpdate() { o.UpdateAt = GetMillis() } + +func IncomingWebhookRequestFromJson(data io.Reader) *IncomingWebhookRequest { + decoder := json.NewDecoder(data) + var o IncomingWebhookRequest + err := decoder.Decode(&o) + if err == nil { + return &o + } else { + return nil + } +} diff --git a/model/outgoing_webhook.go b/model/outgoing_webhook.go index 8958dd5b0..9a1b89a85 100644 --- a/model/outgoing_webhook.go +++ b/model/outgoing_webhook.go @@ -100,6 +100,12 @@ func (o *OutgoingWebhook) IsValid() *AppError { return NewAppError("OutgoingWebhook.IsValid", "Invalid callback urls", "") } + for _, callback := range o.CallbackURLs { + if !IsValidHttpUrl(callback) { + return NewAppError("OutgoingWebhook.IsValid", "Invalid callback URLs. Each must be a valid URL and start with http:// or https://", "") + } + } + return nil } diff --git a/model/outgoing_webhook_test.go b/model/outgoing_webhook_test.go index 2ca48c291..0d1cd773e 100644 --- a/model/outgoing_webhook_test.go +++ b/model/outgoing_webhook_test.go @@ -80,6 +80,11 @@ func TestOutgoingWebhookIsValid(t *testing.T) { t.Fatal("should be invalid") } + o.CallbackURLs = []string{"nowhere.com/"} + if err := o.IsValid(); err == nil { + t.Fatal("should be invalid") + } + o.CallbackURLs = []string{"http://nowhere.com/"} if err := o.IsValid(); err != nil { t.Fatal(err) diff --git a/model/post_list.go b/model/post_list.go index 862673ef3..4c0f5408e 100644 --- a/model/post_list.go +++ b/model/post_list.go @@ -54,6 +54,15 @@ func (o *PostList) AddPost(post *Post) { o.Posts[post.Id] = post } +func (o *PostList) Extend(other *PostList) { + for _, postId := range other.Order { + if _, ok := o.Posts[postId]; !ok { + o.AddPost(other.Posts[postId]) + o.AddOrder(postId) + } + } +} + func (o *PostList) Etag() string { id := "0" diff --git a/model/post_list_test.go b/model/post_list_test.go index 8a34327ce..9ce6447e1 100644 --- a/model/post_list_test.go +++ b/model/post_list_test.go @@ -34,3 +34,37 @@ func TestPostListJson(t *testing.T) { t.Fatal("failed to serialize") } } + +func TestPostListExtend(t *testing.T) { + l1 := PostList{} + + p1 := &Post{Id: NewId(), Message: NewId()} + l1.AddPost(p1) + l1.AddOrder(p1.Id) + + p2 := &Post{Id: NewId(), Message: NewId()} + l1.AddPost(p2) + l1.AddOrder(p2.Id) + + l2 := PostList{} + + p3 := &Post{Id: NewId(), Message: NewId()} + l2.AddPost(p3) + l2.AddOrder(p3.Id) + + l2.Extend(&l1) + + if len(l1.Posts) != 2 || len(l1.Order) != 2 { + t.Fatal("extending l2 changed l1") + } else if len(l2.Posts) != 3 { + t.Fatal("failed to extend posts l2") + } else if l2.Order[0] != p3.Id || l2.Order[1] != p1.Id || l2.Order[2] != p2.Id { + t.Fatal("failed to extend order of l2") + } + + if len(l1.Posts) != 2 || len(l1.Order) != 2 { + t.Fatal("extending l2 again changed l1") + } else if len(l2.Posts) != 3 || len(l2.Order) != 3 { + t.Fatal("extending l2 again changed l2") + } +} diff --git a/model/search_params.go b/model/search_params.go index 7eeeed10f..144e8e461 100644 --- a/model/search_params.go +++ b/model/search_params.go @@ -8,10 +8,10 @@ import ( ) type SearchParams struct { - Terms string - IsHashtag bool - InChannel string - FromUser string + Terms string + IsHashtag bool + InChannels []string + FromUsers []string } var searchFlags = [...]string{"from", "channel", "in"} @@ -31,9 +31,9 @@ func splitWords(text string) []string { return words } -func parseSearchFlags(input []string) ([]string, map[string]string) { +func parseSearchFlags(input []string) ([]string, [][2]string) { words := []string{} - flags := make(map[string]string) + flags := [][2]string{} skipNextWord := false for i, word := range input { @@ -52,10 +52,10 @@ func parseSearchFlags(input []string) ([]string, map[string]string) { // check for case insensitive equality if strings.EqualFold(flag, searchFlag) { if value != "" { - flags[searchFlag] = value + flags = append(flags, [2]string{searchFlag, value}) isFlag = true } else if i < len(input)-1 { - flags[searchFlag] = input[i+1] + flags = append(flags, [2]string{searchFlag, input[i+1]}) skipNextWord = true isFlag = true } @@ -75,56 +75,66 @@ func parseSearchFlags(input []string) ([]string, map[string]string) { return words, flags } -func ParseSearchParams(text string) (*SearchParams, *SearchParams) { +func ParseSearchParams(text string) []*SearchParams { words, flags := parseSearchFlags(splitWords(text)) - hashtagTerms := []string{} - plainTerms := []string{} + hashtagTermList := []string{} + plainTermList := []string{} for _, word := range words { if validHashtag.MatchString(word) { - hashtagTerms = append(hashtagTerms, word) + hashtagTermList = append(hashtagTermList, word) } else { - plainTerms = append(plainTerms, word) + plainTermList = append(plainTermList, word) } } - inChannel := flags["channel"] - if inChannel == "" { - inChannel = flags["in"] + hashtagTerms := strings.Join(hashtagTermList, " ") + plainTerms := strings.Join(plainTermList, " ") + + inChannels := []string{} + fromUsers := []string{} + + for _, flagPair := range flags { + flag := flagPair[0] + value := flagPair[1] + + if flag == "in" || flag == "channel" { + inChannels = append(inChannels, value) + } else if flag == "from" { + fromUsers = append(fromUsers, value) + } } - fromUser := flags["from"] + paramsList := []*SearchParams{} - var plainParams *SearchParams if len(plainTerms) > 0 { - plainParams = &SearchParams{ - Terms: strings.Join(plainTerms, " "), - IsHashtag: false, - InChannel: inChannel, - FromUser: fromUser, - } + paramsList = append(paramsList, &SearchParams{ + Terms: plainTerms, + IsHashtag: false, + InChannels: inChannels, + FromUsers: fromUsers, + }) } - var hashtagParams *SearchParams if len(hashtagTerms) > 0 { - hashtagParams = &SearchParams{ - Terms: strings.Join(hashtagTerms, " "), - IsHashtag: true, - InChannel: inChannel, - FromUser: fromUser, - } + paramsList = append(paramsList, &SearchParams{ + Terms: hashtagTerms, + IsHashtag: true, + InChannels: inChannels, + FromUsers: fromUsers, + }) } // special case for when no terms are specified but we still have a filter - if plainParams == nil && hashtagParams == nil && (inChannel != "" || fromUser != "") { - plainParams = &SearchParams{ - Terms: "", - IsHashtag: false, - InChannel: inChannel, - FromUser: fromUser, - } + if len(plainTerms) == 0 && len(hashtagTerms) == 0 { + paramsList = append(paramsList, &SearchParams{ + Terms: "", + IsHashtag: true, + InChannels: inChannels, + FromUsers: fromUsers, + }) } - return plainParams, hashtagParams + return paramsList } diff --git a/model/search_params_test.go b/model/search_params_test.go index 2eba20f4c..e03e82c5a 100644 --- a/model/search_params_test.go +++ b/model/search_params_test.go @@ -28,25 +28,25 @@ func TestParseSearchFlags(t *testing.T) { if words, flags := parseSearchFlags(splitWords("apple banana from:chan")); len(words) != 2 || words[0] != "apple" || words[1] != "banana" { t.Fatalf("got incorrect words %v", words) - } else if len(flags) != 1 || flags["from"] != "chan" { + } else if len(flags) != 1 || flags[0][0] != "from" || flags[0][1] != "chan" { t.Fatalf("got incorrect flags %v", flags) } if words, flags := parseSearchFlags(splitWords("apple banana from: chan")); len(words) != 2 || words[0] != "apple" || words[1] != "banana" { t.Fatalf("got incorrect words %v", words) - } else if len(flags) != 1 || flags["from"] != "chan" { + } else if len(flags) != 1 || flags[0][0] != "from" || flags[0][1] != "chan" { t.Fatalf("got incorrect flags %v", flags) } if words, flags := parseSearchFlags(splitWords("apple banana in: chan")); len(words) != 2 || words[0] != "apple" || words[1] != "banana" { t.Fatalf("got incorrect words %v", words) - } else if len(flags) != 1 || flags["in"] != "chan" { + } else if len(flags) != 1 || flags[0][0] != "in" || flags[0][1] != "chan" { t.Fatalf("got incorrect flags %v", flags) } if words, flags := parseSearchFlags(splitWords("apple banana channel:chan")); len(words) != 2 || words[0] != "apple" || words[1] != "banana" { t.Fatalf("got incorrect words %v", words) - } else if len(flags) != 1 || flags["channel"] != "chan" { + } else if len(flags) != 1 || flags[0][0] != "channel" || flags[0][1] != "chan" { t.Fatalf("got incorrect flags %v", flags) } @@ -64,7 +64,14 @@ func TestParseSearchFlags(t *testing.T) { if words, flags := parseSearchFlags(splitWords("channel: first in: second from:")); len(words) != 1 || words[0] != "from:" { t.Fatalf("got incorrect words %v", words) - } else if len(flags) != 2 || flags["channel"] != "first" || flags["in"] != "second" { + } else if len(flags) != 2 || flags[0][0] != "channel" || flags[0][1] != "first" || flags[1][0] != "in" || flags[1][1] != "second" { + t.Fatalf("got incorrect flags %v", flags) + } + + if words, flags := parseSearchFlags(splitWords("channel: first channel: second from: third from: fourth")); len(words) != 0 { + t.Fatalf("got incorrect words %v", words) + } else if len(flags) != 4 || flags[0][0] != "channel" || flags[0][1] != "first" || flags[1][0] != "channel" || flags[1][1] != "second" || + flags[2][0] != "from" || flags[2][1] != "third" || flags[3][0] != "from" || flags[3][1] != "fourth" { t.Fatalf("got incorrect flags %v", flags) } } diff --git a/model/team.go b/model/team.go index 9da2cd5b2..5c9cf5a26 100644 --- a/model/team.go +++ b/model/team.go @@ -17,16 +17,19 @@ const ( ) type Team struct { - Id string `json:"id"` - CreateAt int64 `json:"create_at"` - UpdateAt int64 `json:"update_at"` - DeleteAt int64 `json:"delete_at"` - DisplayName string `json:"display_name"` - Name string `json:"name"` - Email string `json:"email"` - Type string `json:"type"` - CompanyName string `json:"company_name"` - AllowedDomains string `json:"allowed_domains"` + Id string `json:"id"` + CreateAt int64 `json:"create_at"` + UpdateAt int64 `json:"update_at"` + DeleteAt int64 `json:"delete_at"` + DisplayName string `json:"display_name"` + Name string `json:"name"` + Email string `json:"email"` + Type string `json:"type"` + CompanyName string `json:"company_name"` + AllowedDomains string `json:"allowed_domains"` + InviteId string `json:"invite_id"` + AllowOpenInvite bool `json:"allow_open_invite"` + AllowTeamListing bool `json:"allow_team_listing"` } type Invites struct { @@ -119,7 +122,7 @@ func (o *Team) IsValid(restrictTeamNames bool) *AppError { return NewAppError("Team.IsValid", "Invalid email", "id="+o.Id) } - if len(o.DisplayName) > 64 { + if len(o.DisplayName) == 0 || len(o.DisplayName) > 64 { return NewAppError("Team.IsValid", "Invalid name", "id="+o.Id) } @@ -157,6 +160,10 @@ func (o *Team) PreSave() { o.CreateAt = GetMillis() o.UpdateAt = o.CreateAt + + if len(o.InviteId) == 0 { + o.InviteId = NewId() + } } func (o *Team) PreUpdate() { @@ -222,6 +229,5 @@ func (o *Team) PreExport() { func (o *Team) Sanitize() { o.Email = "" - o.Type = "" o.AllowedDomains = "" } diff --git a/model/utils.go b/model/utils.go index bb0669df7..681ade870 100644 --- a/model/utils.go +++ b/model/utils.go @@ -11,6 +11,7 @@ import ( "fmt" "io" "net/mail" + "net/url" "regexp" "strings" "time" @@ -301,3 +302,15 @@ var UrlRegex = regexp.MustCompile(`^((?:[a-z]+:\/\/)?(?:(?:[a-z0-9\-]+\.)+(?:[a- var PartialUrlRegex = regexp.MustCompile(`/([A-Za-z0-9]{26})/([A-Za-z0-9]{26})/((?:[A-Za-z0-9]{26})?.+(?:\.[A-Za-z0-9]{3,})?)`) var SplitRunes = map[rune]bool{',': true, ' ': true, '.': true, '!': true, '?': true, ':': true, ';': true, '\n': true, '<': true, '>': true, '(': true, ')': true, '{': true, '}': true, '[': true, ']': true, '+': true, '/': true, '\\': true} + +func IsValidHttpUrl(rawUrl string) bool { + if strings.Index(rawUrl, "http://") != 0 && strings.Index(rawUrl, "https://") != 0 { + return false + } + + if _, err := url.ParseRequestURI(rawUrl); err != nil { + return false + } + + return true +} |