PLT-7567: Integration of Team Icons (#8284)
* PLT-7567: Integration of Team Icons * PLT-7567: Read replica workaround, upgrade logic moved, more concrete i18n key * PLT-7567: Read replica workaround, corrections * PLT-7567: upgrade correction
Этот коммит содержится в:
коммит произвёл
Joram Wilander
родитель
51c7198d53
Коммит
2b3b6051d2
83
api4/team.go
83
api4/team.go
@@ -6,6 +6,7 @@ package api4
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@@ -28,6 +29,10 @@ func (api *API) InitTeam() {
|
|||||||
api.BaseRoutes.Team.Handle("", api.ApiSessionRequired(deleteTeam)).Methods("DELETE")
|
api.BaseRoutes.Team.Handle("", api.ApiSessionRequired(deleteTeam)).Methods("DELETE")
|
||||||
api.BaseRoutes.Team.Handle("/patch", api.ApiSessionRequired(patchTeam)).Methods("PUT")
|
api.BaseRoutes.Team.Handle("/patch", api.ApiSessionRequired(patchTeam)).Methods("PUT")
|
||||||
api.BaseRoutes.Team.Handle("/stats", api.ApiSessionRequired(getTeamStats)).Methods("GET")
|
api.BaseRoutes.Team.Handle("/stats", api.ApiSessionRequired(getTeamStats)).Methods("GET")
|
||||||
|
|
||||||
|
api.BaseRoutes.Team.Handle("/image", api.ApiSessionRequiredTrustRequester(getTeamIcon)).Methods("GET")
|
||||||
|
api.BaseRoutes.Team.Handle("/image", api.ApiSessionRequired(setTeamIcon)).Methods("POST")
|
||||||
|
|
||||||
api.BaseRoutes.TeamMembers.Handle("", api.ApiSessionRequired(getTeamMembers)).Methods("GET")
|
api.BaseRoutes.TeamMembers.Handle("", api.ApiSessionRequired(getTeamMembers)).Methods("GET")
|
||||||
api.BaseRoutes.TeamMembers.Handle("/ids", api.ApiSessionRequired(getTeamMembersByIds)).Methods("POST")
|
api.BaseRoutes.TeamMembers.Handle("/ids", api.ApiSessionRequired(getTeamMembersByIds)).Methods("POST")
|
||||||
api.BaseRoutes.TeamMembersForUser.Handle("", api.ApiSessionRequired(getTeamMembersForUser)).Methods("GET")
|
api.BaseRoutes.TeamMembersForUser.Handle("", api.ApiSessionRequired(getTeamMembersForUser)).Methods("GET")
|
||||||
@@ -729,3 +734,81 @@ func getInviteInfo(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write([]byte(model.MapToJson(result)))
|
w.Write([]byte(model.MapToJson(result)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getTeamIcon(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.RequireTeamId()
|
||||||
|
if c.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.App.SessionHasPermissionToTeam(c.Session, c.Params.TeamId, model.PERMISSION_VIEW_TEAM) {
|
||||||
|
c.SetPermissionError(model.PERMISSION_VIEW_TEAM)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if team, err := c.App.GetTeam(c.Params.TeamId); err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
etag := strconv.FormatInt(team.LastTeamIconUpdate, 10)
|
||||||
|
|
||||||
|
if c.HandleEtag(etag, "Get Team Icon", w, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if img, err := c.App.GetTeamIcon(team); err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
w.Header().Set("Content-Type", "image/png")
|
||||||
|
w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%v, public", 24*60*60)) // 24 hrs
|
||||||
|
w.Header().Set(model.HEADER_ETAG_SERVER, etag)
|
||||||
|
w.Write(img)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTeamIcon(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.RequireTeamId()
|
||||||
|
if c.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.App.SessionHasPermissionToTeam(c.Session, c.Params.TeamId, model.PERMISSION_MANAGE_TEAM) {
|
||||||
|
c.SetPermissionError(model.PERMISSION_MANAGE_TEAM)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.ContentLength > *c.App.Config().FileSettings.MaxFileSize {
|
||||||
|
c.Err = model.NewAppError("setTeamIcon", "api.team.set_team_icon.too_large.app_error", nil, "", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.ParseMultipartForm(*c.App.Config().FileSettings.MaxFileSize); err != nil {
|
||||||
|
c.Err = model.NewAppError("setTeamIcon", "api.team.set_team_icon.parse.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m := r.MultipartForm
|
||||||
|
|
||||||
|
imageArray, ok := m.File["image"]
|
||||||
|
if !ok {
|
||||||
|
c.Err = model.NewAppError("setTeamIcon", "api.team.set_team_icon.no_file.app_error", nil, "", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(imageArray) <= 0 {
|
||||||
|
c.Err = model.NewAppError("setTeamIcon", "api.team.set_team_icon.array.app_error", nil, "", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
imageData := imageArray[0]
|
||||||
|
|
||||||
|
if err := c.App.SetTeamIcon(c.Params.TeamId, imageData); err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.LogAudit("")
|
||||||
|
ReturnStatusOK(w)
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import (
|
|||||||
|
|
||||||
"github.com/mattermost/mattermost-server/model"
|
"github.com/mattermost/mattermost-server/model"
|
||||||
"github.com/mattermost/mattermost-server/utils"
|
"github.com/mattermost/mattermost-server/utils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreateTeam(t *testing.T) {
|
func TestCreateTeam(t *testing.T) {
|
||||||
@@ -1915,3 +1917,82 @@ func TestGetTeamInviteInfo(t *testing.T) {
|
|||||||
_, resp = Client.GetTeamInviteInfo("junk")
|
_, resp = Client.GetTeamInviteInfo("junk")
|
||||||
CheckNotFoundStatus(t, resp)
|
CheckNotFoundStatus(t, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetTeamIcon(t *testing.T) {
|
||||||
|
th := Setup().InitBasic().InitSystemAdmin()
|
||||||
|
defer th.TearDown()
|
||||||
|
Client := th.Client
|
||||||
|
team := th.BasicTeam
|
||||||
|
|
||||||
|
data, err := readTestFile("test.png")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
th.LoginTeamAdmin()
|
||||||
|
|
||||||
|
ok, resp := Client.SetTeamIcon(team.Id, data)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal(resp.Error)
|
||||||
|
}
|
||||||
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
|
ok, resp = Client.SetTeamIcon(model.NewId(), data)
|
||||||
|
if ok {
|
||||||
|
t.Fatal("Should return false, set team icon not allowed")
|
||||||
|
}
|
||||||
|
CheckForbiddenStatus(t, resp)
|
||||||
|
|
||||||
|
th.LoginBasic()
|
||||||
|
|
||||||
|
_, resp = Client.SetTeamIcon(team.Id, data)
|
||||||
|
if resp.StatusCode == http.StatusForbidden {
|
||||||
|
CheckForbiddenStatus(t, resp)
|
||||||
|
} else if resp.StatusCode == http.StatusUnauthorized {
|
||||||
|
CheckUnauthorizedStatus(t, resp)
|
||||||
|
} else {
|
||||||
|
t.Fatal("Should have failed either forbidden or unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.Logout()
|
||||||
|
|
||||||
|
_, resp = Client.SetTeamIcon(team.Id, data)
|
||||||
|
if resp.StatusCode == http.StatusForbidden {
|
||||||
|
CheckForbiddenStatus(t, resp)
|
||||||
|
} else if resp.StatusCode == http.StatusUnauthorized {
|
||||||
|
CheckUnauthorizedStatus(t, resp)
|
||||||
|
} else {
|
||||||
|
t.Fatal("Should have failed either forbidden or unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
teamBefore, err := th.App.GetTeam(team.Id)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
_, resp = th.SystemAdminClient.SetTeamIcon(team.Id, data)
|
||||||
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
|
teamAfter, err := th.App.GetTeam(team.Id)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.True(t, teamBefore.LastTeamIconUpdate < teamAfter.LastTeamIconUpdate, "LastTeamIconUpdate should have been updated for team")
|
||||||
|
|
||||||
|
info := &model.FileInfo{Path: "teams/" + team.Id + "/teamIcon.png"}
|
||||||
|
if err := th.cleanupTestFile(info); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTeamIcon(t *testing.T) {
|
||||||
|
th := Setup().InitBasic().InitSystemAdmin()
|
||||||
|
defer th.TearDown()
|
||||||
|
Client := th.Client
|
||||||
|
team := th.BasicTeam
|
||||||
|
|
||||||
|
// should always fail because no initial image and no auto creation
|
||||||
|
_, resp := Client.GetTeamIcon(team.Id, "")
|
||||||
|
CheckNotFoundStatus(t, resp)
|
||||||
|
|
||||||
|
Client.Logout()
|
||||||
|
|
||||||
|
_, resp = Client.GetTeamIcon(team.Id, "")
|
||||||
|
CheckUnauthorizedStatus(t, resp)
|
||||||
|
}
|
||||||
|
|||||||
90
app/team.go
90
app/team.go
@@ -4,13 +4,18 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/png"
|
||||||
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
l4g "github.com/alecthomas/log4go"
|
l4g "github.com/alecthomas/log4go"
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/model"
|
"github.com/mattermost/mattermost-server/model"
|
||||||
"github.com/mattermost/mattermost-server/utils"
|
"github.com/mattermost/mattermost-server/utils"
|
||||||
@@ -919,3 +924,88 @@ func (a *App) SanitizeTeams(session model.Session, teams []*model.Team) []*model
|
|||||||
|
|
||||||
return teams
|
return teams
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) GetTeamIcon(team *model.Team) ([]byte, *model.AppError) {
|
||||||
|
if len(*a.Config().FileSettings.DriverName) == 0 {
|
||||||
|
return nil, model.NewAppError("GetTeamIcon", "api.team.get_team_icon.filesettings_no_driver.app_error", nil, "", http.StatusNotImplemented)
|
||||||
|
} else {
|
||||||
|
path := "teams/" + team.Id + "/teamIcon.png"
|
||||||
|
if data, err := a.ReadFile(path); err != nil {
|
||||||
|
return nil, model.NewAppError("GetTeamIcon", "api.team.get_team_icon.read_file.app_error", nil, err.Error(), http.StatusNotFound)
|
||||||
|
} else {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) SetTeamIcon(teamId string, imageData *multipart.FileHeader) *model.AppError {
|
||||||
|
file, err := imageData.Open()
|
||||||
|
if err != nil {
|
||||||
|
return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.open.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
return a.SetTeamIconFromFile(teamId, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) SetTeamIconFromFile(teamId string, file multipart.File) *model.AppError {
|
||||||
|
|
||||||
|
team, getTeamErr := a.GetTeam(teamId)
|
||||||
|
|
||||||
|
if getTeamErr != nil {
|
||||||
|
return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.get_team.app_error", nil, getTeamErr.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(*a.Config().FileSettings.DriverName) == 0 {
|
||||||
|
return model.NewAppError("setTeamIcon", "api.team.set_team_icon.storage.app_error", nil, "", http.StatusNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode image config first to check dimensions before loading the whole thing into memory later on
|
||||||
|
config, _, err := image.DecodeConfig(file)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.decode_config.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||||
|
} else if config.Width*config.Height > model.MaxImageSize {
|
||||||
|
return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.too_large.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Seek(0, 0)
|
||||||
|
|
||||||
|
// Decode image into Image object
|
||||||
|
img, _, err := image.Decode(file)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.decode.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Seek(0, 0)
|
||||||
|
|
||||||
|
orientation, _ := getImageOrientation(file)
|
||||||
|
img = makeImageUpright(img, orientation)
|
||||||
|
|
||||||
|
// Scale team icon
|
||||||
|
teamIconWidthAndHeight := 128
|
||||||
|
img = imaging.Fill(img, teamIconWidthAndHeight, teamIconWidthAndHeight, imaging.Center, imaging.Lanczos)
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
err = png.Encode(buf, img)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.encode.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
path := "teams/" + teamId + "/teamIcon.png"
|
||||||
|
|
||||||
|
if err := a.WriteFile(buf.Bytes(), path); err != nil {
|
||||||
|
return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.write_file.app_error", nil, "", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
curTime := model.GetMillis()
|
||||||
|
|
||||||
|
if result := <-a.Srv.Store.Team().UpdateLastTeamIconUpdate(teamId, curTime); result.Err != nil {
|
||||||
|
return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.update.app_error", nil, result.Err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// manually set time to avoid possible cluster inconsistencies
|
||||||
|
team.LastTeamIconUpdate = curTime
|
||||||
|
|
||||||
|
a.sendTeamEvent(team, model.WEBSOCKET_EVENT_UPDATE_TEAM)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
44
i18n/en.json
44
i18n/en.json
@@ -2198,6 +2198,50 @@
|
|||||||
"id": "api.system.go_routines",
|
"id": "api.system.go_routines",
|
||||||
"translation": "The number of running goroutines is over the health threshold %v of %v"
|
"translation": "The number of running goroutines is over the health threshold %v of %v"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.team.set_team_icon.get_team.app_error",
|
||||||
|
"translation": "An error occurred getting the team"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.team.set_team_icon.storage.app_error",
|
||||||
|
"translation": "Unable to upload team icon. Image storage is not configured."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.team.set_team_icon.too_large.app_error",
|
||||||
|
"translation": "Unable to upload team icon. File is too large."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.team.set_team_icon.parse.app_error",
|
||||||
|
"translation": "Could not parse multipart form"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.team.set_team_icon.no_file.app_error",
|
||||||
|
"translation": "No file under 'image' in request"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.team.set_team_icon.array.app_error",
|
||||||
|
"translation": "Empty array under 'image' in request"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.team.set_team_icon.open.app_error",
|
||||||
|
"translation": "Could not open image file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.team.set_team_icon.decode_config.app_error",
|
||||||
|
"translation": "Could not decode team icon metadata"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.team.set_team_icon.decode.app_error",
|
||||||
|
"translation": "Could not decode team icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.team.set_team_icon.encode.app_error",
|
||||||
|
"translation": "Could not encode team icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.team.set_team_icon.write_file.app_error",
|
||||||
|
"translation": "Could not save team icon"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.team.add_user_to_team.added",
|
"id": "api.team.add_user_to_team.added",
|
||||||
"translation": "%v added to the team by %v."
|
"translation": "%v added to the team by %v."
|
||||||
|
|||||||
@@ -3318,3 +3318,56 @@ func (c *Client4) DeactivatePlugin(id string) (bool, *Response) {
|
|||||||
return CheckStatusOK(r), BuildResponse(r)
|
return CheckStatusOK(r), BuildResponse(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetTeamIcon sets team icon of the team
|
||||||
|
func (c *Client4) SetTeamIcon(teamId string, data []byte) (bool, *Response) {
|
||||||
|
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
|
||||||
|
if part, err := writer.CreateFormFile("image", "teamIcon.png"); err != nil {
|
||||||
|
return false, &Response{Error: NewAppError("SetTeamIcon", "model.client.set_team_icon.no_file.app_error", nil, err.Error(), http.StatusBadRequest)}
|
||||||
|
} else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil {
|
||||||
|
return false, &Response{Error: NewAppError("SetTeamIcon", "model.client.set_team_icon.no_file.app_error", nil, err.Error(), http.StatusBadRequest)}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
return false, &Response{Error: NewAppError("SetTeamIcon", "model.client.set_team_icon.writer.app_error", nil, err.Error(), http.StatusBadRequest)}
|
||||||
|
}
|
||||||
|
|
||||||
|
rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetTeamRoute(teamId)+"/image", bytes.NewReader(body.Bytes()))
|
||||||
|
rq.Header.Set("Content-Type", writer.FormDataContentType())
|
||||||
|
rq.Close = true
|
||||||
|
|
||||||
|
if len(c.AuthToken) > 0 {
|
||||||
|
rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil {
|
||||||
|
// set to http.StatusForbidden(403)
|
||||||
|
return false, &Response{StatusCode: http.StatusForbidden, Error: NewAppError(c.GetTeamRoute(teamId)+"/image", "model.client.connecting.app_error", nil, err.Error(), 403)}
|
||||||
|
} else {
|
||||||
|
defer closeBody(rp)
|
||||||
|
|
||||||
|
if rp.StatusCode >= 300 {
|
||||||
|
return false, BuildErrorResponse(rp, AppErrorFromJson(rp.Body))
|
||||||
|
} else {
|
||||||
|
return CheckStatusOK(rp), BuildResponse(rp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTeamIcon gets the team icon of the team
|
||||||
|
func (c *Client4) GetTeamIcon(teamId, etag string) ([]byte, *Response) {
|
||||||
|
if r, err := c.DoApiGet(c.GetTeamRoute(teamId)+"/image", etag); err != nil {
|
||||||
|
return nil, BuildErrorResponse(r, err)
|
||||||
|
} else {
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
|
if data, err := ioutil.ReadAll(r.Body); err != nil {
|
||||||
|
return nil, BuildErrorResponse(r, NewAppError("GetTeamIcon", "model.client.get_team_icon.app_error", nil, err.Error(), r.StatusCode))
|
||||||
|
} else {
|
||||||
|
return data, BuildResponse(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,19 +26,20 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Team struct {
|
type Team struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
CreateAt int64 `json:"create_at"`
|
CreateAt int64 `json:"create_at"`
|
||||||
UpdateAt int64 `json:"update_at"`
|
UpdateAt int64 `json:"update_at"`
|
||||||
DeleteAt int64 `json:"delete_at"`
|
DeleteAt int64 `json:"delete_at"`
|
||||||
DisplayName string `json:"display_name"`
|
DisplayName string `json:"display_name"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
CompanyName string `json:"company_name"`
|
CompanyName string `json:"company_name"`
|
||||||
AllowedDomains string `json:"allowed_domains"`
|
AllowedDomains string `json:"allowed_domains"`
|
||||||
InviteId string `json:"invite_id"`
|
InviteId string `json:"invite_id"`
|
||||||
AllowOpenInvite bool `json:"allow_open_invite"`
|
AllowOpenInvite bool `json:"allow_open_invite"`
|
||||||
|
LastTeamIconUpdate int64 `json:"last_team_icon_update,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TeamPatch struct {
|
type TeamPatch struct {
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ func (s SqlTeamStore) Update(team *model.Team) store.StoreChannel {
|
|||||||
team.CreateAt = oldTeam.CreateAt
|
team.CreateAt = oldTeam.CreateAt
|
||||||
team.UpdateAt = model.GetMillis()
|
team.UpdateAt = model.GetMillis()
|
||||||
team.Name = oldTeam.Name
|
team.Name = oldTeam.Name
|
||||||
|
team.LastTeamIconUpdate = oldTeam.LastTeamIconUpdate
|
||||||
|
|
||||||
if count, err := s.GetMaster().Update(team); err != nil {
|
if count, err := s.GetMaster().Update(team); err != nil {
|
||||||
result.Err = model.NewAppError("SqlTeamStore.Update", "store.sql_team.update.updating.app_error", nil, "id="+team.Id+", "+err.Error(), http.StatusInternalServerError)
|
result.Err = model.NewAppError("SqlTeamStore.Update", "store.sql_team.update.updating.app_error", nil, "id="+team.Id+", "+err.Error(), http.StatusInternalServerError)
|
||||||
@@ -559,3 +560,13 @@ func (s SqlTeamStore) RemoveAllMembersByUser(userId string) store.StoreChannel {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (us SqlTeamStore) UpdateLastTeamIconUpdate(teamId string, curTime int64) store.StoreChannel {
|
||||||
|
return store.Do(func(result *store.StoreResult) {
|
||||||
|
if _, err := us.GetMaster().Exec("UPDATE Teams SET LastTeamIconUpdate = :Time, UpdateAt = :Time WHERE Id = :teamId", map[string]interface{}{"Time": curTime, "teamId": teamId}); err != nil {
|
||||||
|
result.Err = model.NewAppError("SqlTeamStore.UpdateLastTeamIconUpdate", "store.sql_team.update_last_team_icon_update.app_error", nil, "team_id="+teamId, http.StatusInternalServerError)
|
||||||
|
} else {
|
||||||
|
result.Data = teamId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -365,8 +365,10 @@ func UpgradeDatabaseToVersion471(sqlStore SqlStore) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UpgradeDatabaseToVersion48(sqlStore SqlStore) {
|
func UpgradeDatabaseToVersion48(sqlStore SqlStore) {
|
||||||
|
|
||||||
//TODO: Uncomment the following condition when version 4.8.0 is released
|
//TODO: Uncomment the following condition when version 4.8.0 is released
|
||||||
//if shouldPerformUpgrade(sqlStore, VERSION_4_7_0, VERSION_4_8_0) {
|
//if shouldPerformUpgrade(sqlStore, VERSION_4_7_0, VERSION_4_8_0) {
|
||||||
|
sqlStore.CreateColumnIfNotExists("Teams", "LastTeamIconUpdate", "bigint", "bigint", "0")
|
||||||
// saveSchemaVersion(sqlStore, VERSION_4_8_0)
|
// saveSchemaVersion(sqlStore, VERSION_4_8_0)
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ type TeamStore interface {
|
|||||||
RemoveMember(teamId string, userId string) StoreChannel
|
RemoveMember(teamId string, userId string) StoreChannel
|
||||||
RemoveAllMembersByTeam(teamId string) StoreChannel
|
RemoveAllMembersByTeam(teamId string) StoreChannel
|
||||||
RemoveAllMembersByUser(userId string) StoreChannel
|
RemoveAllMembersByUser(userId string) StoreChannel
|
||||||
|
UpdateLastTeamIconUpdate(teamId string, curTime int64) StoreChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChannelStore interface {
|
type ChannelStore interface {
|
||||||
|
|||||||
@@ -476,3 +476,19 @@ func (_m *TeamStore) UpdateMember(member *model.TeamMember) store.StoreChannel {
|
|||||||
|
|
||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateLastTeamIconUpdate provides a mock function with given fields: teamId
|
||||||
|
func (_m *TeamStore) UpdateLastTeamIconUpdate(teamId string, curTime int64) store.StoreChannel {
|
||||||
|
ret := _m.Called(teamId)
|
||||||
|
|
||||||
|
var r0 store.StoreChannel
|
||||||
|
if rf, ok := ret.Get(0).(func(string, int64) store.StoreChannel); ok {
|
||||||
|
r0 = rf(teamId, curTime)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(store.StoreChannel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ func TestTeamStore(t *testing.T, ss store.Store) {
|
|||||||
t.Run("MemberCount", func(t *testing.T) { testTeamStoreMemberCount(t, ss) })
|
t.Run("MemberCount", func(t *testing.T) { testTeamStoreMemberCount(t, ss) })
|
||||||
t.Run("GetChannelUnreadsForAllTeams", func(t *testing.T) { testGetChannelUnreadsForAllTeams(t, ss) })
|
t.Run("GetChannelUnreadsForAllTeams", func(t *testing.T) { testGetChannelUnreadsForAllTeams(t, ss) })
|
||||||
t.Run("GetChannelUnreadsForTeam", func(t *testing.T) { testGetChannelUnreadsForTeam(t, ss) })
|
t.Run("GetChannelUnreadsForTeam", func(t *testing.T) { testGetChannelUnreadsForTeam(t, ss) })
|
||||||
|
t.Run("UpdateLastTeamIconUpdate", func(t *testing.T) { testUpdateLastTeamIconUpdate(t, ss) })
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTeamStoreSave(t *testing.T, ss store.Store) {
|
func testTeamStoreSave(t *testing.T, ss store.Store) {
|
||||||
@@ -1003,3 +1004,28 @@ func testGetChannelUnreadsForTeam(t *testing.T, ss store.Store) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testUpdateLastTeamIconUpdate(t *testing.T, ss store.Store) {
|
||||||
|
|
||||||
|
// team icon initially updated a second ago
|
||||||
|
lastTeamIconUpdateInitial := model.GetMillis() - 1000
|
||||||
|
|
||||||
|
o1 := &model.Team{}
|
||||||
|
o1.DisplayName = "Display Name"
|
||||||
|
o1.Name = "z-z-z" + model.NewId() + "b"
|
||||||
|
o1.Email = model.NewId() + "@nowhere.com"
|
||||||
|
o1.Type = model.TEAM_OPEN
|
||||||
|
o1.LastTeamIconUpdate = lastTeamIconUpdateInitial
|
||||||
|
o1 = (<-ss.Team().Save(o1)).Data.(*model.Team)
|
||||||
|
|
||||||
|
curTime := model.GetMillis()
|
||||||
|
|
||||||
|
if err := (<-ss.Team().UpdateLastTeamIconUpdate(o1.Id, curTime)).Err; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ro1 := (<-ss.Team().Get(o1.Id)).Data.(*model.Team)
|
||||||
|
if ro1.LastTeamIconUpdate <= lastTeamIconUpdateInitial {
|
||||||
|
t.Fatal("LastTeamIconUpdate not updated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user