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
Этот коммит содержится в:
коммит произвёл
Christopher Speller
родитель
661f221727
Коммит
a0cc913b85
@@ -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
Обычный файл
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
Обычный файл
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)
|
||||
}
|
||||
}
|
||||
Ссылка в новой задаче
Block a user