PLT-3143 Added serverside code for custom Emoji (#3311)

* Added model objects for emoji

* Added database tables for emoji

* Added settings for custom emoji

* Added serverside APIs and unit tests for custom emoji

* Added additional validation to catch duplicate emoji names earlier on

* Added additional validation to prevent users from adding emoji as another user
Этот коммит содержится в:
Harrison Healey
2016-06-14 09:38:19 -04:00
коммит произвёл Christopher Speller
родитель 661f221727
Коммит a0cc913b85
18 изменённых файлов: 1538 добавлений и 5 удалений

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

@@ -7,7 +7,9 @@ import (
"bytes"
"fmt"
l4g "github.com/alecthomas/log4go"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"strconv"
@@ -106,6 +108,10 @@ func (c *Client) GetChannelNameRoute(channelName string) string {
return fmt.Sprintf("/teams/%v/channels/name/%v", c.GetTeamId(), channelName)
}
func (c *Client) GetEmojiRoute() string {
return "/emoji"
}
func (c *Client) GetGeneralRoute() string {
return "/general"
}
@@ -185,6 +191,17 @@ func (c *Client) Must(result *Result, err *AppError) *Result {
return result
}
// MustGeneric is a convenience function used for testing.
func (c *Client) MustGeneric(result interface{}, err *AppError) interface{} {
if err != nil {
l4g.Close()
time.Sleep(time.Second)
panic(err)
}
return result
}
// CheckStatusOK is a convenience function for checking the return of Web Service
// call that return the a map of status=OK.
func (c *Client) CheckStatusOK(r *http.Response) bool {
@@ -1509,3 +1526,74 @@ func (c *Client) GetInitialLoad() (*Result, *AppError) {
r.Header.Get(HEADER_ETAG_SERVER), InitialLoadFromJson(r.Body)}, nil
}
}
// ListEmoji returns a list of all user-created emoji for the server.
func (c *Client) ListEmoji() ([]*Emoji, *AppError) {
if r, err := c.DoApiGet(c.GetEmojiRoute()+"/list", "", ""); err != nil {
return nil, err
} else {
defer closeBody(r)
c.fillInExtraProperties(r)
return EmojiListFromJson(r.Body), nil
}
}
// CreateEmoji will save an emoji to the server if the current user has permission
// to do so. If successful, the provided emoji will be returned with its Id field
// filled in. Otherwise, an error will be returned.
func (c *Client) CreateEmoji(emoji *Emoji, image []byte, filename string) (*Emoji, *AppError) {
c.clearExtraProperties()
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
if part, err := writer.CreateFormFile("image", filename); err != nil {
return nil, NewLocAppError("CreateEmoji", "model.client.create_emoji.image.app_error", nil, err.Error())
} else if _, err = io.Copy(part, bytes.NewBuffer(image)); err != nil {
return nil, NewLocAppError("CreateEmoji", "model.client.create_emoji.image.app_error", nil, err.Error())
}
if err := writer.WriteField("emoji", emoji.ToJson()); err != nil {
return nil, NewLocAppError("CreateEmoji", "model.client.create_emoji.emoji.app_error", nil, err.Error())
}
if err := writer.Close(); err != nil {
return nil, NewLocAppError("CreateEmoji", "model.client.create_emoji.writer.app_error", nil, err.Error())
}
rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetEmojiRoute()+"/create", body)
rq.Header.Set("Content-Type", writer.FormDataContentType())
if len(c.AuthToken) > 0 {
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
}
if r, err := c.HttpClient.Do(rq); err != nil {
return nil, NewLocAppError("CreateEmoji", "model.client.connecting.app_error", nil, err.Error())
} else if r.StatusCode >= 300 {
return nil, AppErrorFromJson(r.Body)
} else {
defer closeBody(r)
c.fillInExtraProperties(r)
return EmojiFromJson(r.Body), nil
}
}
// DeleteEmoji will delete an emoji from the server if the current user has permission
// to do so. If successful, it will return status=ok. Otherwise, an error will be returned.
func (c *Client) DeleteEmoji(id string) (bool, *AppError) {
data := map[string]string{"id": id}
if r, err := c.DoApiPost(c.GetEmojiRoute()+"/delete", MapToJson(data)); err != nil {
return false, err
} else {
c.fillInExtraProperties(r)
return c.CheckStatusOK(r), nil
}
}
// GetCustomEmojiImageUrl returns the API route that can be used to get the image used by
// the given emoji.
func (c *Client) GetCustomEmojiImageUrl(id string) string {
return c.GetEmojiRoute() + "/" + id
}

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

@@ -34,6 +34,9 @@ const (
DIRECT_MESSAGE_TEAM = "team"
FAKE_SETTING = "********************************"
RESTRICT_EMOJI_CREATION_ALL = "all"
RESTRICT_EMOJI_CREATION_ADMIN = "system_admin"
)
// should match the values in webapp/i18n/i18n.jsx
@@ -70,6 +73,8 @@ type ServiceSettings struct {
WebsocketSecurePort *int
WebsocketPort *int
WebserverMode *string
EnableCustomEmoji *bool
RestrictCustomEmojiCreation *string
}
type SSOSettings struct {
@@ -565,6 +570,16 @@ func (o *Config) SetDefaults() {
*o.ServiceSettings.WebserverMode = "regular"
}
if o.ServiceSettings.EnableCustomEmoji == nil {
o.ServiceSettings.EnableCustomEmoji = new(bool)
*o.ServiceSettings.EnableCustomEmoji = true
}
if o.ServiceSettings.RestrictCustomEmojiCreation == nil {
o.ServiceSettings.RestrictCustomEmojiCreation = new(string)
*o.ServiceSettings.RestrictCustomEmojiCreation = RESTRICT_EMOJI_CREATION_ALL
}
if o.ComplianceSettings.Enable == nil {
o.ComplianceSettings.Enable = new(bool)
*o.ComplianceSettings.Enable = false

95
model/emoji.go Обычный файл
Просмотреть файл

@@ -0,0 +1,95 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
type Emoji struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
CreatorId string `json:"creator_id"`
Name string `json:"name"`
}
func (emoji *Emoji) IsValid() *AppError {
if len(emoji.Id) != 26 {
return NewLocAppError("Emoji.IsValid", "model.emoji.id.app_error", nil, "")
}
if emoji.CreateAt == 0 {
return NewLocAppError("Emoji.IsValid", "model.emoji.create_at.app_error", nil, "id="+emoji.Id)
}
if emoji.UpdateAt == 0 {
return NewLocAppError("Emoji.IsValid", "model.emoji.update_at.app_error", nil, "id="+emoji.Id)
}
if len(emoji.CreatorId) != 26 {
return NewLocAppError("Emoji.IsValid", "model.emoji.user_id.app_error", nil, "")
}
if len(emoji.Name) == 0 || len(emoji.Name) > 64 {
return NewLocAppError("Emoji.IsValid", "model.emoji.name.app_error", nil, "")
}
return nil
}
func (emoji *Emoji) PreSave() {
if emoji.Id == "" {
emoji.Id = NewId()
}
emoji.CreateAt = GetMillis()
emoji.UpdateAt = emoji.CreateAt
}
func (emoji *Emoji) PreUpdate() {
emoji.UpdateAt = GetMillis()
}
func (emoji *Emoji) ToJson() string {
b, err := json.Marshal(emoji)
if err != nil {
return ""
} else {
return string(b)
}
}
func EmojiFromJson(data io.Reader) *Emoji {
decoder := json.NewDecoder(data)
var emoji Emoji
err := decoder.Decode(&emoji)
if err == nil {
return &emoji
} else {
return nil
}
}
func EmojiListToJson(emojiList []*Emoji) string {
b, err := json.Marshal(emojiList)
if err != nil {
return ""
} else {
return string(b)
}
}
func EmojiListFromJson(data io.Reader) []*Emoji {
decoder := json.NewDecoder(data)
var emojiList []*Emoji
err := decoder.Decode(&emojiList)
if err == nil {
return emojiList
} else {
return nil
}
}

63
model/emoji_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,63 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"strings"
"testing"
)
func TestEmojiIsValid(t *testing.T) {
emoji := Emoji{
Id: NewId(),
CreateAt: 1234,
UpdateAt: 1234,
DeleteAt: 0,
CreatorId: NewId(),
Name: "name",
}
if err := emoji.IsValid(); err != nil {
t.Fatal(err)
}
emoji.Id = "1234"
if err := emoji.IsValid(); err == nil {
t.Fatal()
}
emoji.Id = NewId()
emoji.CreateAt = 0
if err := emoji.IsValid(); err == nil {
t.Fatal()
}
emoji.CreateAt = 1234
emoji.UpdateAt = 0
if err := emoji.IsValid(); err == nil {
t.Fatal()
}
emoji.UpdateAt = 1234
emoji.CreatorId = strings.Repeat("1", 25)
if err := emoji.IsValid(); err == nil {
t.Fatal()
}
emoji.CreatorId = strings.Repeat("1", 27)
if err := emoji.IsValid(); err == nil {
t.Fatal()
}
emoji.CreatorId = NewId()
emoji.Name = strings.Repeat("1", 65)
if err := emoji.IsValid(); err == nil {
t.Fatal()
}
emoji.Name = strings.Repeat("1", 64)
if err := emoji.IsValid(); err != nil {
t.Fatal(err)
}
}