```release-note
NONE
```
Этот коммит содержится в:
Agniva De Sarker
2023-10-12 09:47:35 +05:30
коммит произвёл GitHub
родитель d172ff1881
Коммит 3dd9e3715c
49 изменённых файлов: 12 добавлений и 4727 удалений

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

@@ -50,10 +50,6 @@ ifeq ($(BUILD_NUMBER),)
BUILD_NUMBER := dev BUILD_NUMBER := dev
endif endif
ifeq ($(BUILD_NUMBER),dev)
export MM_FEATUREFLAGS_GRAPHQL = true
endif
# Ensure developer invocation and tests are anchored. # Ensure developer invocation and tests are anchored.
MM_SERVER_PATH ?= $(ROOT) MM_SERVER_PATH ?= $(ROOT)

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

@@ -7,7 +7,6 @@ import (
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
graphql "github.com/graph-gophers/graphql-go"
_ "github.com/mattermost/go-i18n/i18n" _ "github.com/mattermost/go-i18n/i18n"
"github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/model"
@@ -142,7 +141,6 @@ type Routes struct {
type API struct { type API struct {
srv *app.Server srv *app.Server
schema *graphql.Schema
BaseRoutes *Routes BaseRoutes *Routes
} }
@@ -306,9 +304,6 @@ func Init(srv *app.Server) (*API, error) {
api.InitUsage() api.InitUsage()
api.InitHostedCustomer() api.InitHostedCustomer()
api.InitDrafts() api.InitDrafts()
if err := api.InitGraphQL(); err != nil {
return nil, err
}
srv.Router.Handle("/api/v4/{anything:.*}", http.HandlerFunc(api.Handle404)) srv.Router.Handle("/api/v4/{anything:.*}", http.HandlerFunc(api.Handle404))

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

@@ -4,9 +4,7 @@
package api4 package api4
import ( import (
"bytes"
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@@ -21,7 +19,6 @@ import (
"time" "time"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
graphql "github.com/graph-gophers/graphql-go"
s3 "github.com/minio/minio-go/v7" s3 "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials" "github.com/minio/minio-go/v7/pkg/credentials"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -47,7 +44,6 @@ type TestHelper struct {
Context *request.Context Context *request.Context
Client *model.Client4 Client *model.Client4
GraphQLClient *graphQLClient
BasicUser *model.User BasicUser *model.User
BasicUser2 *model.User BasicUser2 *model.User
TeamAdminUser *model.User TeamAdminUser *model.User
@@ -196,7 +192,6 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent
} }
th.Client = th.CreateClient() th.Client = th.CreateClient()
th.GraphQLClient = newGraphQLClient(fmt.Sprintf("http://localhost:%v", th.App.Srv().ListenAddr.Port))
th.SystemAdminClient = th.CreateClient() th.SystemAdminClient = th.CreateClient()
th.SystemManagerClient = th.CreateClient() th.SystemManagerClient = th.CreateClient()
@@ -811,16 +806,10 @@ func (th *TestHelper) CreateDmChannel(user *model.User) *model.Channel {
func (th *TestHelper) LoginBasic() { func (th *TestHelper) LoginBasic() {
th.LoginBasicWithClient(th.Client) th.LoginBasicWithClient(th.Client)
if os.Getenv("MM_FEATUREFLAGS_GRAPHQL") == "true" {
th.LoginBasicWithGraphQL()
}
} }
func (th *TestHelper) LoginBasic2() { func (th *TestHelper) LoginBasic2() {
th.LoginBasic2WithClient(th.Client) th.LoginBasic2WithClient(th.Client)
if os.Getenv("MM_FEATUREFLAGS_GRAPHQL") == "true" {
th.LoginBasicWithGraphQL()
}
} }
func (th *TestHelper) LoginTeamAdmin() { func (th *TestHelper) LoginTeamAdmin() {
@@ -842,13 +831,6 @@ func (th *TestHelper) LoginBasicWithClient(client *model.Client4) {
} }
} }
func (th *TestHelper) LoginBasicWithGraphQL() {
_, _, err := th.GraphQLClient.login(th.BasicUser.Email, th.BasicUser.Password)
if err != nil {
panic(err)
}
}
func (th *TestHelper) LoginBasic2WithClient(client *model.Client4) { func (th *TestHelper) LoginBasic2WithClient(client *model.Client4) {
_, _, err := client.Login(context.Background(), th.BasicUser2.Email, th.BasicUser2.Password) _, _, err := client.Login(context.Background(), th.BasicUser2.Email, th.BasicUser2.Password)
if err != nil { if err != nil {
@@ -1301,22 +1283,3 @@ func (th *TestHelper) SetupScheme(scope string) *model.Scheme {
} }
return scheme return scheme
} }
func (th *TestHelper) MakeGraphQLRequest(input *graphQLInput) (*graphql.Response, error) {
url := fmt.Sprintf("http://localhost:%v", th.App.Srv().ListenAddr.Port) + model.APIURLSuffixV5 + "/graphql"
buf, err := json.Marshal(input)
if err != nil {
panic(err)
}
resp, err := th.GraphQLClient.doAPIRequest("POST", url, bytes.NewReader(buf), map[string]string{})
if err != nil {
panic(err)
}
defer closeBody(resp)
var gqlResp *graphql.Response
err = json.NewDecoder(resp.Body).Decode(&gqlResp)
return gqlResp, err
}

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

@@ -1,193 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"context"
_ "embed"
"encoding/json"
"net/http"
"github.com/graph-gophers/dataloader/v6"
graphql "github.com/graph-gophers/graphql-go"
gqlerrors "github.com/graph-gophers/graphql-go/errors"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/v8/channels/web"
)
type graphQLInput struct {
Query string `json:"query"`
OperationName string `json:"operationName"`
Variables map[string]any `json:"variables"`
}
// Unique type to hold our context.
type ctxKey int
const (
webCtx ctxKey = 0
rolesLoaderCtx ctxKey = 1
channelsLoaderCtx ctxKey = 2
teamsLoaderCtx ctxKey = 3
usersLoaderCtx ctxKey = 4
)
const loaderBatchCapacity = web.PerPageMaximum
//go:embed schema.graphqls
var schemaRaw string
func (api *API) InitGraphQL() error {
// Guard with a feature flag.
if !api.srv.Config().FeatureFlags.GraphQL {
return nil
}
var err error
opts := []graphql.SchemaOpt{
graphql.UseFieldResolvers(),
graphql.Logger(mlog.NewGraphQLLogger(api.srv.Log())),
graphql.MaxParallelism(loaderBatchCapacity), // This is dangerous if the query
// uses any non-dataloader backed object. So we need to be a bit careful here.
}
if isProd() {
opts = append(opts,
// MaxDepth cannot be moved as a general param
// because otherwise introspection also doesn't work
// with just a depth of 4.
graphql.MaxDepth(4),
graphql.DisableIntrospection(),
)
}
api.schema, err = graphql.ParseSchema(schemaRaw, &resolver{}, opts...)
if err != nil {
return err
}
api.BaseRoutes.APIRoot5.Handle("/graphql", api.APIHandlerTrustRequester(graphiQL)).Methods("GET")
api.BaseRoutes.APIRoot5.Handle("/graphql", api.APISessionRequired(api.graphQL)).Methods("POST")
return nil
}
func (api *API) graphQL(c *Context, w http.ResponseWriter, r *http.Request) {
var response *graphql.Response
defer func() {
if response != nil {
if err := json.NewEncoder(w).Encode(response); err != nil {
c.Logger.Warn("Error while writing response", mlog.Err(err))
}
}
}()
// Limit bodies to 100KiB.
// We need to enforce a lower limit than the file upload size,
// to prevent the library doing unnecessary parsing.
r.Body = http.MaxBytesReader(w, r.Body, 102400)
var params graphQLInput
if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
err2 := gqlerrors.Errorf("invalid request body: %v", err)
response = &graphql.Response{Errors: []*gqlerrors.QueryError{err2}}
return
}
if isProd() && params.OperationName == "" {
err2 := gqlerrors.Errorf("operation name not passed")
response = &graphql.Response{Errors: []*gqlerrors.QueryError{err2}}
return
}
c.GraphQLOperationName = params.OperationName
// Populate the context with required info.
reqCtx := r.Context()
reqCtx = context.WithValue(reqCtx, webCtx, c)
rolesLoader := dataloader.NewBatchedLoader(graphQLRolesLoader, dataloader.WithBatchCapacity(loaderBatchCapacity))
reqCtx = context.WithValue(reqCtx, rolesLoaderCtx, rolesLoader)
channelsLoader := dataloader.NewBatchedLoader(graphQLChannelsLoader, dataloader.WithBatchCapacity(loaderBatchCapacity))
reqCtx = context.WithValue(reqCtx, channelsLoaderCtx, channelsLoader)
teamsLoader := dataloader.NewBatchedLoader(graphQLTeamsLoader, dataloader.WithBatchCapacity(loaderBatchCapacity))
reqCtx = context.WithValue(reqCtx, teamsLoaderCtx, teamsLoader)
usersLoader := dataloader.NewBatchedLoader(graphQLUsersLoader, dataloader.WithBatchCapacity(loaderBatchCapacity))
reqCtx = context.WithValue(reqCtx, usersLoaderCtx, usersLoader)
response = api.schema.Exec(reqCtx,
params.Query,
params.OperationName,
params.Variables)
if len(response.Errors) > 0 {
logFunc := mlog.Error
for _, gqlErr := range response.Errors {
if gqlErr.Err != nil {
if appErr, ok := gqlErr.Err.(*model.AppError); ok && appErr.StatusCode < http.StatusInternalServerError {
logFunc = mlog.Debug
break
}
}
}
logFunc("Error executing request", mlog.String("operation", params.OperationName),
mlog.Array("errors", response.Errors))
}
}
func graphiQL(c *Context, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
w.Write(graphiqlPage)
}
var graphiqlPage = []byte(`
<!DOCTYPE html>
<html>
<head>
<title>GraphiQL editor | Mattermost</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.11.11/graphiql.min.css" integrity="sha256-gSgd+on4bTXigueyd/NSRNAy4cBY42RAVNaXnQDjOW8=" crossorigin="anonymous"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-promise/4.1.1/es6-promise.auto.min.js" integrity="sha256-OI3N9zCKabDov2rZFzl8lJUXCcP7EmsGcGoP6DMXQCo=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.3/fetch.min.js" integrity="sha256-aB35laj7IZhLTx58xw/Gm1EKOoJJKZt6RY+bH1ReHxs=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.production.min.js" integrity="sha256-wouRkivKKXA3y6AuyFwcDcF50alCNV8LbghfYCH6Z98=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.production.min.js" integrity="sha256-9hrJxD4IQsWHdNpzLkJKYGiY/SEZFJJSUqyeZPNKd8g=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.11.11/graphiql.min.js" integrity="sha256-oeWyQyKKUurcnbFRsfeSgrdOpXXiRYopnPjTVZ+6UmI=" crossorigin="anonymous"></script>
</head>
<body style="width: 100%; height: 100%; margin: 0; overflow: hidden;">
<div id="graphiql" style="height: 100vh;">Loading...</div>
<script>
function graphQLFetcher(graphQLParams) {
return fetch("/api/v5/graphql", {
method: "post",
body: JSON.stringify(graphQLParams),
credentials: "include",
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
}).then(function (response) {
return response.text();
}).then(function (responseBody) {
try {
return JSON.parse(responseBody);
} catch (error) {
return responseBody;
}
});
}
ReactDOM.render(
React.createElement(GraphiQL, {fetcher: graphQLFetcher}),
document.getElementById("graphiql")
);
</script>
</body>
</html>
`)
// isProd is a helper function to apply prod-specific graphQL validations.
func isProd() bool {
return model.BuildNumber != "dev"
}

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

@@ -1,87 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"encoding/json"
"io"
"net/http"
"strings"
"github.com/mattermost/mattermost/server/public/model"
)
// graphQLClient is an internal test client to run the tests.
// When the API matures, we will expose it to the model package.
type graphQLClient struct {
URL string // The location of the server, for example "http://localhost:8065"
APIURL string // The api location of the server, for example "http://localhost:8065/api/v4"
httpClient *http.Client // The http client
authToken string
authType string
httpHeader map[string]string // Headers to be copied over for each request
}
func newGraphQLClient(url string) *graphQLClient {
url = strings.TrimRight(url, "/")
return &graphQLClient{url, url + model.APIURLSuffix, &http.Client{}, "", "", map[string]string{}}
}
func (c *graphQLClient) login(loginId string, password string) (*model.User, *model.Response, error) {
m := make(map[string]string)
m["login_id"] = loginId
m["password"] = password
r, err := c.doAPIRequest(http.MethodPost, c.APIURL+"/users/login", strings.NewReader(model.MapToJSON(m)), map[string]string{model.HeaderEtagClient: ""})
if err != nil {
return nil, model.BuildResponse(r), err
}
defer closeBody(r)
c.authToken = r.Header.Get(model.HeaderToken)
c.authType = model.HeaderBearer
var user model.User
if jsonErr := json.NewDecoder(r.Body).Decode(&user); jsonErr != nil {
return nil, nil, model.NewAppError("login", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr)
}
return &user, model.BuildResponse(r), nil
}
func (c *graphQLClient) doAPIRequest(method, url string, data io.Reader, headers map[string]string) (*http.Response, error) {
rq, err := c.prepareRequest(method, url, data, headers)
if err != nil {
return nil, err
}
rp, err := c.httpClient.Do(rq)
if err != nil {
return rp, err
}
return rp, nil
}
func (c *graphQLClient) prepareRequest(method, url string, data io.Reader, headers map[string]string) (*http.Request, error) {
rq, err := http.NewRequest(method, url, data)
if err != nil {
return nil, err
}
for k, v := range headers {
rq.Header.Set(k, v)
}
if c.authToken != "" {
rq.Header.Set(model.HeaderAuth, c.authType+" "+c.authToken)
}
if c.httpHeader != nil && len(c.httpHeader) > 0 {
for k, v := range c.httpHeader {
rq.Header.Set(k, v)
}
}
return rq, nil
}

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

@@ -1,34 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"os"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func TestGraphQLPayload(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
th := Setup(t).InitBasic()
defer th.TearDown()
largeString := strings.Repeat("hello", 204800)
input := graphQLInput{
OperationName: "config",
Query: largeString,
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 1)
// The actual error isn't exposed. We compare the string
// to not confuse with other errors.
require.Contains(t, resp.Errors[0].Message, "request body too large")
}

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

@@ -1,427 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"context"
"errors"
"fmt"
"github.com/graph-gophers/dataloader/v6"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/v8/channels/app"
"github.com/mattermost/mattermost/server/v8/channels/store"
"github.com/mattermost/mattermost/server/v8/channels/web"
)
// cursorPrefix is used to categorize objects
// sent in a cursor. The type is prepended
// to the string with a - to find which
// object the id belongs to.
//
// And after the type is extracted, object
// specific logic can be applied to extract the id.
type cursorPrefix string
const (
channelMemberCursorPrefix cursorPrefix = "channelMember"
channelCursorPrefix cursorPrefix = "channel"
)
type resolver struct {
}
// match with api4.getChannelsForTeamForUser
func (r *resolver) Channels(ctx context.Context, args struct {
TeamID string
UserID string
IncludeDeleted bool
LastDeleteAt float64
LastUpdateAt float64
First int32
After string
}) ([]*channel, error) {
c, err := getCtx(ctx)
if err != nil {
return nil, err
}
if args.UserID == model.Me {
args.UserID = c.AppContext.Session().UserId
}
if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), args.UserID) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return nil, c.Err
}
if args.TeamID != "" && !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), args.TeamID, model.PermissionViewTeam) {
c.SetPermissionError(model.PermissionViewTeam)
return nil, c.Err
}
limit := int(args.First)
// ensure args.First limit
if limit == 0 {
limit = web.PerPageDefault
} else if limit > web.PerPageMaximum {
return nil, fmt.Errorf("first parameter %d higher than allowed maximum of %d", limit, web.PerPageMaximum)
}
// ensure args.After format
var afterChannel string
var ok bool
if args.After != "" {
afterChannel, ok = parseChannelCursor(args.After)
if !ok {
return nil, fmt.Errorf("after cursor not in the correct format: %s", args.After)
}
}
// TODO: convert this to a streaming API.
channels, appErr := c.App.GetChannelsForTeamForUserWithCursor(c.AppContext, args.TeamID, args.UserID, &model.ChannelSearchOpts{
IncludeDeleted: args.IncludeDeleted,
LastDeleteAt: int(args.LastDeleteAt),
LastUpdateAt: int(args.LastUpdateAt),
PerPage: model.NewInt(limit),
}, afterChannel)
if appErr != nil {
return nil, appErr
}
appErr = c.App.FillInChannelsProps(c.AppContext, channels)
if appErr != nil {
return nil, appErr
}
return postProcessChannels(c, channels)
}
// match with api4.getUser
func (r *resolver) User(ctx context.Context, args struct{ ID string }) (*user, error) {
return getGraphQLUser(ctx, args.ID)
}
// match with api4.getClientConfig
func (r *resolver) Config(ctx context.Context) (model.StringMap, error) {
c, err := getCtx(ctx)
if err != nil {
return nil, err
}
if c.AppContext.Session().UserId == "" {
return c.App.Srv().Platform().LimitedClientConfigWithComputed(), nil
}
return c.App.Srv().Platform().ClientConfigWithComputed(), nil
}
// match with api4.getClientLicense
func (r *resolver) License(ctx context.Context) (model.StringMap, error) {
c, err := getCtx(ctx)
if err != nil {
return nil, err
}
if c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionReadLicenseInformation) {
return c.App.Srv().ClientLicense(), nil
}
return c.App.Srv().GetSanitizedClientLicense(), nil
}
// match with api4.getTeamMembersForUser for teamID=""
// and api4.getTeamMember for teamID != ""
func (r *resolver) TeamMembers(ctx context.Context, args struct {
UserID string
TeamID string
ExcludeTeam bool
}) ([]*teamMember, error) {
c, err := getCtx(ctx)
if err != nil {
return nil, err
}
if args.UserID == model.Me {
args.UserID = c.AppContext.Session().UserId
}
if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), args.UserID) && !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionReadOtherUsersTeams) {
c.SetPermissionError(model.PermissionReadOtherUsersTeams)
return nil, c.Err
}
canSee, appErr := c.App.UserCanSeeOtherUser(c.AppContext, c.AppContext.Session().UserId, args.UserID)
if appErr != nil {
return nil, appErr
}
if !canSee {
c.SetPermissionError(model.PermissionViewMembers)
return nil, c.Err
}
if args.TeamID != "" && !args.ExcludeTeam {
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), args.TeamID, model.PermissionViewTeam) {
c.SetPermissionError(model.PermissionViewTeam)
return nil, c.Err
}
tm, appErr2 := c.App.GetTeamMember(c.AppContext, args.TeamID, args.UserID)
if appErr2 != nil {
return nil, appErr2
}
return []*teamMember{{*tm}}, nil
}
excludeTeamID := ""
if args.TeamID != "" && args.ExcludeTeam {
excludeTeamID = args.TeamID
}
// Do not return archived team members
members, appErr := c.App.GetTeamMembersForUser(c.AppContext, args.UserID, excludeTeamID, false)
if appErr != nil {
return nil, appErr
}
// Convert to the wrapper format.
res := make([]*teamMember, 0, len(members))
for _, tm := range members {
res = append(res, &teamMember{*tm})
}
return res, nil
}
func (*resolver) ChannelsLeft(ctx context.Context, args struct {
UserID string
Since float64
}) ([]string, error) {
c, err := getCtx(ctx)
if err != nil {
return nil, err
}
if args.UserID == model.Me {
args.UserID = c.AppContext.Session().UserId
}
if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), args.UserID) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return nil, c.Err
}
return c.App.Srv().Store().ChannelMemberHistory().GetChannelsLeftSince(args.UserID, int64(args.Since))
}
// match with api4.getChannelMember
func (*resolver) ChannelMembers(ctx context.Context, args struct {
UserID string
TeamID string
ChannelID string
ExcludeTeam bool
First int32
After string
LastUpdateAt float64
}) ([]*channelMember, error) {
c, err := getCtx(ctx)
if err != nil {
return nil, err
}
if args.UserID == model.Me {
args.UserID = c.AppContext.Session().UserId
}
// If it's a single channel
if args.ChannelID != "" {
if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), args.ChannelID, model.PermissionReadChannel) {
c.SetPermissionError(model.PermissionReadChannel)
return nil, c.Err
}
ctx := c.AppContext
ctx.SetContext(app.WithMaster(ctx.Context()))
member, appErr := c.App.GetChannelMember(ctx, args.ChannelID, args.UserID)
if appErr != nil {
return nil, appErr
}
return []*channelMember{{*member}}, nil
}
if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), args.UserID) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return nil, c.Err
}
limit := int(args.First)
// ensure args.First limit
if limit == 0 {
limit = web.PerPageDefault
} else if limit > web.PerPageMaximum {
return nil, fmt.Errorf("first parameter %d higher than allowed maximum of %d", limit, web.PerPageMaximum)
}
// ensure args.After format
var afterChannel, afterUser string
var ok bool
if args.After != "" {
afterChannel, afterUser, ok = parseChannelMemberCursor(args.After)
if !ok {
return nil, fmt.Errorf("after cursor not in the correct format: %s", args.After)
}
}
if args.TeamID != "" {
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), args.TeamID, model.PermissionViewTeam) {
primaryTeam := *c.App.Config().TeamSettings.ExperimentalPrimaryTeam
if primaryTeam != "" {
team, appErr := c.App.GetTeamByName(primaryTeam)
if appErr != nil {
return []*channelMember{}, appErr
}
args.TeamID = team.Id
} else {
return []*channelMember{}, nil
}
}
}
opts := &store.ChannelMemberGraphQLSearchOpts{
AfterChannel: afterChannel,
AfterUser: afterUser,
Limit: limit,
LastUpdateAt: int(args.LastUpdateAt),
ExcludeTeam: args.ExcludeTeam,
}
members, err := c.App.Srv().Store().Channel().GetMembersForUserWithCursor(args.UserID, args.TeamID, opts)
if err != nil {
return nil, err
}
res := make([]*channelMember, 0, len(members))
for _, cm := range members {
res = append(res, &channelMember{cm})
}
return res, nil
}
// match with api4.getCategoriesForTeamForUser
func (*resolver) SidebarCategories(ctx context.Context, args struct {
UserID string
TeamID string
ExcludeTeam bool
}) ([]*model.SidebarCategoryWithChannels, error) {
c, err := getCtx(ctx)
if err != nil {
return nil, err
}
// Fallback to primary team logic
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), args.TeamID, model.PermissionViewTeam) {
primaryTeam := *c.App.Config().TeamSettings.ExperimentalPrimaryTeam
if primaryTeam != "" {
team, appErr := c.App.GetTeamByName(primaryTeam)
if appErr != nil {
return []*model.SidebarCategoryWithChannels{}, appErr
}
args.TeamID = team.Id
} else {
return []*model.SidebarCategoryWithChannels{}, nil
}
}
if args.UserID == model.Me {
args.UserID = c.AppContext.Session().UserId
}
if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), args.UserID) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return nil, c.Err
}
// If it's only for a single team.
var categories *model.OrderedSidebarCategories
var appErr *model.AppError
if !args.ExcludeTeam {
categories, appErr = c.App.GetSidebarCategoriesForTeamForUser(c.AppContext, args.UserID, args.TeamID)
if appErr != nil {
return nil, appErr
}
} else {
opts := &store.SidebarCategorySearchOpts{
TeamID: args.TeamID,
ExcludeTeam: args.ExcludeTeam,
}
categories, appErr = c.App.GetSidebarCategories(c.AppContext, args.UserID, opts)
if appErr != nil {
return nil, appErr
}
}
// TODO: look into optimizing this.
// create map
orderMap := make(map[string]*model.SidebarCategoryWithChannels, len(categories.Categories))
for _, category := range categories.Categories {
orderMap[category.Id] = category
}
// create a new slice based on the order
res := make([]*model.SidebarCategoryWithChannels, 0, len(categories.Categories))
for _, categoryId := range categories.Order {
res = append(res, orderMap[categoryId])
}
return res, nil
}
// getCtx extracts web.Context out of the usual request context.
// Kind of an anti-pattern, but there are lots of methods attached to *web.Context
// so we use it for now.
func getCtx(ctx context.Context) (*web.Context, error) {
c, ok := ctx.Value(webCtx).(*web.Context)
if !ok {
return nil, errors.New("no web.Context found in context")
}
return c, nil
}
// getRolesLoader returns the roles loader out of the context.
func getRolesLoader(ctx context.Context) (*dataloader.Loader, error) {
l, ok := ctx.Value(rolesLoaderCtx).(*dataloader.Loader)
if !ok {
return nil, errors.New("no dataloader.Loader found in context")
}
return l, nil
}
// getChannelsLoader returns the channels loader out of the context.
func getChannelsLoader(ctx context.Context) (*dataloader.Loader, error) {
l, ok := ctx.Value(channelsLoaderCtx).(*dataloader.Loader)
if !ok {
return nil, errors.New("no dataloader.Loader found in context")
}
return l, nil
}
// getTeamsLoader returns the teams loader out of the context.
func getTeamsLoader(ctx context.Context) (*dataloader.Loader, error) {
l, ok := ctx.Value(teamsLoaderCtx).(*dataloader.Loader)
if !ok {
return nil, errors.New("no dataloader.Loader found in context")
}
return l, nil
}
// getUsersLoader returns the users loader out of the context.
func getUsersLoader(ctx context.Context) (*dataloader.Loader, error) {
l, ok := ctx.Value(usersLoaderCtx).(*dataloader.Loader)
if !ok {
return nil, errors.New("no dataloader.Loader found in context")
}
return l, nil
}

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

@@ -1,157 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"context"
"encoding/base64"
"fmt"
"sort"
"strings"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/v8/channels/web"
)
// channel is an internal graphQL wrapper struct to add resolver methods.
type channel struct {
model.Channel
PrettyDisplayName string
}
// match with api4.getTeam
func (ch *channel) Team(ctx context.Context) (*model.Team, error) {
if ch.TeamId == "" {
return nil, nil
}
return getGraphQLTeam(ctx, ch.TeamId)
}
func (ch *channel) Cursor() *string {
cursor := string(channelCursorPrefix) + "-" + ch.Id
encoded := base64.StdEncoding.EncodeToString([]byte(cursor))
return model.NewString(encoded)
}
func parseChannelCursor(cursor string) (channelID string, ok bool) {
decoded, err := base64.StdEncoding.DecodeString(cursor)
if err != nil {
return "", false
}
prefix, id, found := strings.Cut(string(decoded), "-")
if !found {
return "", false
}
if cursorPrefix(prefix) != channelCursorPrefix {
return "", false
}
return id, true
}
func postProcessChannels(c *web.Context, channels []*model.Channel) ([]*channel, error) {
// This approach becomes effectively similar to a dataloader if the displayName computation
// were to be done at the field level per channel.
// Get DM/GM channelIDs and set empty maps as well.
var channelIDs []string
for _, ch := range channels {
if ch.IsGroupOrDirect() {
channelIDs = append(channelIDs, ch.Id)
}
// This is needed to avoid sending null, which
// does not match with the schema since props is not nullable.
// And making it nullable would mean taking pointer of a map,
// which is not very idiomatic.
ch.MakeNonNil()
}
var nameFormat string
var userInfo map[string][]*model.User
var err error
// Avoiding unnecessary queries unless necessary.
if len(channelIDs) > 0 {
userInfo, err = c.App.Srv().Store().Channel().GetMembersInfoByChannelIds(channelIDs)
if err != nil {
return nil, err
}
user := &model.User{Id: c.AppContext.Session().UserId}
nameFormat = c.App.GetNotificationNameFormat(user)
}
// Convert to the wrapper format.
nameCache := make(map[string]string)
res := make([]*channel, len(channels))
for i, ch := range channels {
prettyName := ch.DisplayName
if ch.IsGroupOrDirect() {
// get users slice for channel id
users := userInfo[ch.Id]
if users == nil {
return nil, fmt.Errorf("user info not found for channel id: %s", ch.Id)
}
prettyName = getPrettyDNForUsers(nameFormat, users, c.AppContext.Session().UserId, nameCache)
}
res[i] = &channel{Channel: *ch, PrettyDisplayName: prettyName}
}
return res, nil
}
func getPrettyDNForUsers(displaySetting string, users []*model.User, omitUserId string, cache map[string]string) string {
displayNames := make([]string, 0, len(users))
for _, u := range users {
if u.Id == omitUserId {
continue
}
displayNames = append(displayNames, getPrettyDNForUser(displaySetting, u, cache))
}
sort.Strings(displayNames)
result := strings.Join(displayNames, ", ")
if result == "" {
// Self DM
result = getPrettyDNForUser(displaySetting, users[0], cache)
}
return result
}
func getPrettyDNForUser(displaySetting string, user *model.User, cache map[string]string) string {
// use the cache first
if name, ok := cache[user.Id]; ok {
return name
}
var displayName string
switch displaySetting {
case "nickname_full_name":
displayName = user.Nickname
if strings.TrimSpace(displayName) == "" {
displayName = user.GetFullName()
}
if strings.TrimSpace(displayName) == "" {
displayName = user.Username
}
case "full_name":
displayName = user.GetFullName()
if strings.TrimSpace(displayName) == "" {
displayName = user.Username
}
default: // the "username" case also falls under this one.
displayName = user.Username
}
// update the cache
cache[user.Id] = displayName
return displayName
}

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

@@ -1,226 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"context"
"encoding/base64"
"fmt"
"strings"
"github.com/graph-gophers/dataloader/v6"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/v8/channels/web"
)
// channelMember is an internal graphQL wrapper struct to add resolver methods.
type channelMember struct {
model.ChannelMember
}
// match with api4.getUser
func (cm *channelMember) User(ctx context.Context) (*user, error) {
return getGraphQLUser(ctx, cm.UserId)
}
// match with api4.Channel
func (cm *channelMember) Channel(ctx context.Context) (*channel, error) {
loader, err := getChannelsLoader(ctx)
if err != nil {
return nil, err
}
thunk := loader.Load(ctx, dataloader.StringKey(cm.ChannelId))
result, err := thunk()
if err != nil {
return nil, err
}
channel := result.(*channel)
return channel, nil
}
func graphQLChannelsLoader(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
stringKeys := keys.Keys()
result := make([]*dataloader.Result, len(stringKeys))
c, err := getCtx(ctx)
if err != nil {
for i := range result {
result[i] = &dataloader.Result{Error: err}
}
return result
}
channels, err := getGraphQLChannels(c, stringKeys)
if err != nil {
for i := range result {
result[i] = &dataloader.Result{Error: err}
}
return result
}
for i, ch := range channels {
result[i] = &dataloader.Result{Data: ch}
}
return result
}
func getGraphQLChannels(c *web.Context, channelIDs []string) ([]*channel, error) {
channels, appErr := c.App.GetChannels(c.AppContext, channelIDs)
if appErr != nil {
return nil, appErr
}
if len(channels) != len(channelIDs) {
return nil, fmt.Errorf("all channels were not found. Requested %d; Found %d", len(channelIDs), len(channels))
}
var openChannels, nonOpenChannels, teamsForOpenChannels []string
uniqueTeams := make(map[string]bool)
for _, ch := range channels {
if ch.Type == model.ChannelTypeOpen {
openChannels = append(openChannels, ch.Id)
uniqueTeams[ch.TeamId] = true
} else {
nonOpenChannels = append(nonOpenChannels, ch.Id)
}
}
for teamID := range uniqueTeams {
teamsForOpenChannels = append(teamsForOpenChannels, teamID)
}
if len(openChannels) > 0 && !c.App.SessionHasPermissionToChannels(c.AppContext, *c.AppContext.Session(), openChannels, model.PermissionReadChannel) &&
!c.App.SessionHasPermissionToTeams(c.AppContext, *c.AppContext.Session(), teamsForOpenChannels, model.PermissionReadPublicChannel) {
c.SetPermissionError(model.PermissionReadPublicChannel)
return nil, c.Err
}
if len(nonOpenChannels) > 0 && !c.App.SessionHasPermissionToChannels(c.AppContext, *c.AppContext.Session(), nonOpenChannels, model.PermissionReadChannel) {
c.SetPermissionError(model.PermissionReadChannel)
return nil, c.Err
}
appErr = c.App.FillInChannelsProps(c.AppContext, model.ChannelList(channels))
if appErr != nil {
return nil, appErr
}
res, err := postProcessChannels(c, channels)
if err != nil {
return nil, err
}
// The channels need to be in the exact same order as the input slice.
tmp := make(map[string]*channel)
for _, ch := range res {
tmp[ch.Id] = ch
}
// We reuse the same slice and just rewrite the channels.
for i, id := range channelIDs {
res[i] = tmp[id]
}
return res, nil
}
func (cm *channelMember) Roles_(ctx context.Context) ([]*model.Role, error) {
loader, err := getRolesLoader(ctx)
if err != nil {
return nil, err
}
thunk := loader.LoadMany(ctx, dataloader.NewKeysFromStrings(strings.Fields(cm.Roles)))
results, errs := thunk()
// All errors are the same. We just return the first one.
if len(errs) > 0 && errs[0] != nil {
return nil, err
}
roles := make([]*model.Role, len(results))
for i, res := range results {
roles[i] = res.(*model.Role)
}
return roles, nil
}
func (cm *channelMember) Cursor() *string {
cursor := string(channelMemberCursorPrefix) + "-" + cm.ChannelId + "-" + cm.UserId
encoded := base64.StdEncoding.EncodeToString([]byte(cursor))
return model.NewString(encoded)
}
func graphQLRolesLoader(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
stringKeys := keys.Keys()
result := make([]*dataloader.Result, len(stringKeys))
c, err := getCtx(ctx)
if err != nil {
for i := range result {
result[i] = &dataloader.Result{Error: err}
}
return result
}
roles, err := getGraphQLRoles(c, stringKeys)
if err != nil {
for i := range result {
result[i] = &dataloader.Result{Error: err}
}
return result
}
for i, role := range roles {
result[i] = &dataloader.Result{Data: role}
}
return result
}
func getGraphQLRoles(c *web.Context, roleNames []string) ([]*model.Role, error) {
cleanedRoleNames, valid := model.CleanRoleNames(roleNames)
if !valid {
c.SetInvalidParam("rolename")
return nil, c.Err
}
roles, appErr := c.App.GetRolesByNames(cleanedRoleNames)
if appErr != nil {
return nil, appErr
}
// The roles need to be in the exact same order as the input slice.
tmp := make(map[string]*model.Role)
for _, r := range roles {
tmp[r.Name] = r
}
// We reuse the same slice and just rewrite the roles.
for i, roleName := range roleNames {
roles[i] = tmp[roleName]
}
return roles, nil
}
func parseChannelMemberCursor(cursor string) (channelID, userID string, ok bool) {
decoded, err := base64.StdEncoding.DecodeString(cursor)
if err != nil {
return "", "", false
}
parts := strings.Split(string(decoded), "-")
if len(parts) != 3 {
return "", "", false
}
if cursorPrefix(parts[0]) != channelMemberCursorPrefix {
return "", "", false
}
return parts[1], parts[2], true
}

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

@@ -1,400 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"encoding/json"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost/server/public/model"
)
func TestGraphQLChannelMembers(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
th := Setup(t).InitBasic()
defer th.TearDown()
// Adding another team with more channels (public and private)
myTeam := th.CreateTeam()
ch1 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypeOpen, myTeam.Id)
ch2 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypePrivate, myTeam.Id)
th.LinkUserToTeam(th.BasicUser, myTeam)
th.App.AddUserToChannel(th.Context, th.BasicUser, ch1, false)
th.App.AddUserToChannel(th.Context, th.BasicUser, ch2, false)
// Creating some msgcount
th.CreateMessagePostWithClient(th.Client, th.BasicChannel, "basic post")
th.CreateMessagePostWithClient(th.Client, ch1, "ch1 post")
var q struct {
ChannelMembers []struct {
Channel struct {
ID string `json:"id"`
CreateAt float64 `json:"createAt"`
UpdateAt float64 `json:"updateAt"`
Type model.ChannelType `json:"type"`
DisplayName string `json:"displayName"`
Name string `json:"name"`
Header string `json:"header"`
Purpose string `json:"purpose"`
Team struct {
ID string `json:"id"`
} `json:"team"`
} `json:"channel"`
User struct {
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
NickName string `json:"nickname"`
} `json:"user"`
Roles []struct {
ID string `json:"id"`
Name string `json:"Name"`
Permissions []string `json:"permissions"`
SchemeManaged bool `json:"schemeManaged"`
BuiltIn bool `json:"builtIn"`
} `json:"roles"`
LastViewedAt float64 `json:"lastViewedAt"`
LastUpdateAt float64 `json:"lastUpdateAt"`
MsgCount float64 `json:"msgCount"`
MentionCount float64 `json:"mentionCount"`
MentionCountRoot float64 `json:"mentionCountRoot"`
UrgentMentionCount float64 `json:"urgentMentionCount"`
MsgCountRoot float64 `json:"msgCountRoot"`
NotifyProps model.StringMap `json:"notifyProps"`
SchemeGuest bool `json:"schemeGuest"`
SchemeUser bool `json:"schemeUser"`
SchemeAdmin bool `json:"schemeAdmin"`
Cursor string `json:"cursor"`
} `json:"channelMembers"`
}
t.Run("all", func(t *testing.T) {
input := graphQLInput{
OperationName: "channelMembers",
Query: `
query channelMembers {
channelMembers(userId: "me") {
channel {
id
createAt
updateAt
type
displayName
name
header
team {
id
}
}
user {
id
username
email
}
msgCount
mentionCount
mentionCountRoot
urgentMentionCount
msgCountRoot
schemeGuest
schemeUser
schemeAdmin
cursor
}
}
`,
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.ChannelMembers, 9)
numPrivate := 0
numPublic := 0
numOffTopic := 0
numTownSquare := 0
for _, ch := range q.ChannelMembers {
assert.NotEmpty(t, ch.Channel.ID)
assert.NotEmpty(t, ch.Channel.Name)
assert.NotEmpty(t, ch.Channel.CreateAt)
assert.NotEmpty(t, ch.Channel.UpdateAt)
if ch.Channel.Type == model.ChannelTypeOpen {
numPublic++
} else if ch.Channel.Type == model.ChannelTypePrivate {
numPrivate++
}
if ch.Channel.DisplayName == "Off-Topic" {
numOffTopic++
} else if ch.Channel.DisplayName == "Town Square" {
numTownSquare++
}
assert.Equal(t, th.BasicUser.Id, ch.User.ID)
assert.Equal(t, th.BasicUser.Username, ch.User.Username)
assert.Equal(t, th.BasicUser.Email, ch.User.Email)
assert.False(t, ch.SchemeGuest)
if ch.Channel.Team.ID == myTeam.Id {
assert.True(t, ch.SchemeAdmin)
} else {
assert.False(t, ch.SchemeAdmin)
}
assert.True(t, ch.SchemeUser)
assert.NotEmpty(t, ch.Cursor)
switch ch.Channel.ID {
case th.BasicChannel.Id:
assert.Equal(t, float64(2), ch.MsgCount)
case ch1.Id:
assert.Equal(t, float64(1), ch.MsgCount)
}
}
assert.Equal(t, 2, numPrivate)
assert.Equal(t, 7, numPublic)
assert.Equal(t, 2, numOffTopic)
assert.Equal(t, 2, numTownSquare)
})
t.Run("user_perms", func(t *testing.T) {
input := graphQLInput{
OperationName: "channelMembers",
Query: `
query channelMembers($user: String!) {
channelMembers(userId: $user) {
channel {
id
createAt
updateAt
}
msgCount
mentionCount
mentionCountRoot
urgentMentionCount
}
}
`,
Variables: map[string]any{
"user": model.NewId(),
},
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 1)
})
t.Run("pagination", func(t *testing.T) {
query := `query channelMembers($first: Int, $after: String = "") {
channelMembers(userId: "me", first: $first, after: $after) {
channel {
id
createAt
updateAt
type
displayName
name
header
}
cursor
}
}
`
input := graphQLInput{
OperationName: "channelMembers",
Query: query,
Variables: map[string]any{
"first": 4,
},
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.ChannelMembers, 4)
input = graphQLInput{
OperationName: "channelMembers",
Query: query,
Variables: map[string]any{
"first": 4,
"after": q.ChannelMembers[3].Cursor,
},
}
resp, err = th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.ChannelMembers, 4)
input = graphQLInput{
OperationName: "channelMembers",
Query: query,
Variables: map[string]any{
"first": 4,
"after": q.ChannelMembers[3].Cursor,
},
}
resp, err = th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.ChannelMembers, 1)
})
t.Run("channel_filter", func(t *testing.T) {
query := `query channelMembers($channelId: String, $first: Int, $after: String = "") {
channelMembers(userId: "me", channelId: $channelId, first: $first, after: $after) {
channel {
id
}
}
}
`
input := graphQLInput{
OperationName: "channelMembers",
Query: query,
Variables: map[string]any{
"channelId": ch1.Id,
"first": 4,
},
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.ChannelMembers, 1)
assert.Equal(t, q.ChannelMembers[0].Channel.ID, ch1.Id)
input = graphQLInput{
OperationName: "channelMembers",
Query: query,
Variables: map[string]any{
"channelId": model.NewId(),
"first": 3,
},
}
resp, err = th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 1)
})
t.Run("team_filter", func(t *testing.T) {
query := `query channelMembers($teamId: String, $excludeTeam: Boolean = false) {
channelMembers(userId: "me", teamId: $teamId, excludeTeam: $excludeTeam) {
channel {
id
}
}
}
`
input := graphQLInput{
OperationName: "channelMembers",
Query: query,
Variables: map[string]any{
"teamId": th.BasicTeam.Id,
},
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.ChannelMembers, 5)
input = graphQLInput{
OperationName: "channelMembers",
Query: query,
Variables: map[string]any{
"teamId": th.BasicTeam.Id,
"excludeTeam": true,
},
}
resp, err = th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.ChannelMembers, 4)
})
t.Run("UpdateAt", func(t *testing.T) {
query := `query channelMembers($first: Int, $after: String = "", $lastUpdateAt: Float) {
channelMembers(userId: "me", first: $first, after: $after, lastUpdateAt: $lastUpdateAt) {
channel {
id
}
lastUpdateAt
cursor
}
}
`
now := model.GetMillis()
input := graphQLInput{
OperationName: "channelMembers",
Query: query,
Variables: map[string]any{
"first": 4,
"lastUpdateAt": float64(now),
},
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
require.Len(t, q.ChannelMembers, 0)
// Create post to update the lastUpdateAt for the channel member.
th.CreateMessagePostWithClient(th.Client, th.BasicChannel, "another post")
input = graphQLInput{
OperationName: "channelMembers",
Query: query,
Variables: map[string]any{
"first": 4,
"lastUpdateAt": float64(now),
},
}
resp, err = th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
require.Len(t, q.ChannelMembers, 1)
assert.Equal(t, th.BasicChannel.Id, q.ChannelMembers[0].Channel.ID)
assert.GreaterOrEqual(t, q.ChannelMembers[0].LastUpdateAt, float64(now))
})
}
func TestChannelMemberCursor(t *testing.T) {
ch := channelMember{
ChannelMember: model.ChannelMember{ChannelId: "testid", UserId: "userid"},
}
cur := ch.Cursor()
chId, userId, ok := parseChannelMemberCursor(*cur)
require.True(t, ok)
assert.Equal(t, ch.ChannelId, chId)
assert.Equal(t, ch.UserId, userId)
}

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

@@ -1,513 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"context"
"encoding/json"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost/server/public/model"
)
func TestGraphQLChannels(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
th := Setup(t).InitBasic()
defer th.TearDown()
// Adding another team with more channels (public and private)
myTeam := th.CreateTeam()
ch1 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypeOpen, myTeam.Id)
ch2 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypePrivate, myTeam.Id)
th.LinkUserToTeam(th.BasicUser, myTeam)
th.App.AddUserToChannel(th.Context, th.BasicUser, ch1, false)
th.App.AddUserToChannel(th.Context, th.BasicUser, ch2, false)
th.CreateDmChannel(th.BasicUser2)
var q struct {
Channels []struct {
ID string `json:"id"`
CreateAt float64 `json:"createAt"`
UpdateAt float64 `json:"updateAt"`
Type model.ChannelType `json:"type"`
DisplayName string `json:"displayName"`
PrettyDisplayName string `json:"prettyDisplayName"`
Name string `json:"name"`
Header string `json:"header"`
Purpose string `json:"purpose"`
SchemeId string `json:"schemeId"`
TotalMsgCountRoot float64 `json:"totalMsgCountRoot"`
LastRootPostAt float64 `json:"lastRootPostAt"`
Cursor string `json:"cursor"`
Props map[string]any `json:"props"`
Team struct {
ID string `json:"id"`
DisplayName string `json:"displayName"`
} `json:"team"`
} `json:"channels"`
}
t.Run("all", func(t *testing.T) {
input := graphQLInput{
OperationName: "channels",
Query: `
query channels {
channels(userId: "me") {
id
createAt
updateAt
type
displayName
prettyDisplayName
name
header
purpose
schemeId
totalMsgCountRoot
lastRootPostAt
cursor
props
}
}
`,
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.Channels, 10)
numPrivate := 0
numPublic := 0
numOffTopic := 0
numTownSquare := 0
for _, ch := range q.Channels {
assert.NotEmpty(t, ch.ID)
assert.NotEmpty(t, ch.Name)
assert.NotEmpty(t, ch.Cursor)
assert.NotEmpty(t, ch.PrettyDisplayName)
assert.NotEmpty(t, ch.CreateAt)
assert.NotEmpty(t, ch.UpdateAt)
assert.NotNil(t, ch.Props)
if ch.Type == model.ChannelTypeOpen {
numPublic++
} else if ch.Type == model.ChannelTypePrivate {
numPrivate++
}
if ch.DisplayName == "Off-Topic" {
numOffTopic++
} else if ch.DisplayName == "Town Square" {
numTownSquare++
}
}
assert.Equal(t, 2, numPrivate)
assert.Equal(t, 7, numPublic)
assert.Equal(t, 2, numOffTopic)
assert.Equal(t, 2, numTownSquare)
})
t.Run("user_perms", func(t *testing.T) {
query := `query channels($userId: String = "") {
channels(userId: $userId) {
id
createAt
updateAt
type
cursor
}
}
`
u1 := th.CreateUser()
input := graphQLInput{
OperationName: "channels",
Query: query,
Variables: map[string]any{
"userId": u1.Id,
},
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 1)
})
t.Run("pagination", func(t *testing.T) {
query := `query channels($first: Int, $after: String = "") {
channels(userId: "me", first: $first, after: $after) {
id
createAt
updateAt
type
displayName
name
header
purpose
schemeId
cursor
}
}
`
input := graphQLInput{
OperationName: "channels",
Query: query,
Variables: map[string]any{
"first": 4,
},
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.Channels, 4)
input = graphQLInput{
OperationName: "channels",
Query: query,
Variables: map[string]any{
"first": 4,
"after": q.Channels[3].Cursor,
},
}
resp, err = th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.Channels, 4)
input = graphQLInput{
OperationName: "channels",
Query: query,
Variables: map[string]any{
"first": 4,
"after": q.Channels[3].Cursor,
},
}
resp, err = th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.Channels, 2)
})
t.Run("team_filter", func(t *testing.T) {
query := `query channels($teamId: String, $first: Int) {
channels(userId: "me", teamId: $teamId, first: $first) {
id
}
}
`
input := graphQLInput{
OperationName: "channels",
Query: query,
Variables: map[string]any{
"first": 10,
"teamId": myTeam.Id,
},
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.Channels, 5)
input = graphQLInput{
OperationName: "channels",
Query: query,
Variables: map[string]any{
"first": 2,
"teamId": myTeam.Id,
},
}
resp, err = th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.Channels, 2)
})
t.Run("team_data", func(t *testing.T) {
query := `query channels($teamId: String, $first: Int) {
channels(userId: "me", teamId: $teamId, first: $first) {
id
team {
id
displayName
}
}
}
`
input := graphQLInput{
OperationName: "channels",
Query: query,
Variables: map[string]any{
"first": 2,
"teamId": myTeam.Id,
},
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.Channels, 2)
// Iterating because one of them can be a DM channel.
for _, ch := range q.Channels {
if ch.Team.ID != "" {
assert.Equal(t, myTeam.Id, ch.Team.ID)
assert.Equal(t, myTeam.DisplayName, ch.Team.DisplayName)
}
}
})
t.Run("Delete+Update", func(t *testing.T) {
query := `query channels($lastDeleteAt: Float = 0,
$lastUpdateAt: Float = 0,
$first: Int = 60,
$includeDeleted: Boolean) {
channels(userId: "me", lastDeleteAt: $lastDeleteAt, lastUpdateAt: $lastUpdateAt, first: $first, includeDeleted: $includeDeleted) {
id
}
}
`
input := graphQLInput{
OperationName: "channels",
Query: query,
Variables: map[string]any{
"includeDeleted": false,
},
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.Channels, 10)
now := model.GetMillis()
input = graphQLInput{
OperationName: "channels",
Query: query,
Variables: map[string]any{
"includeDeleted": true,
"lastUpdateAt": float64(now),
},
}
resp, err = th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0) // no errors for no channels found
th.BasicChannel.Purpose = "newpurpose"
_, _, err = th.Client.UpdateChannel(context.Background(), th.BasicChannel)
require.NoError(t, err)
input = graphQLInput{
OperationName: "channels",
Query: query,
Variables: map[string]any{
"includeDeleted": true,
"lastUpdateAt": float64(now),
},
}
resp, err = th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.Channels, 1)
_, err = th.Client.DeleteChannel(context.Background(), ch1.Id)
require.NoError(t, err)
_, err = th.Client.DeleteChannel(context.Background(), ch2.Id)
require.NoError(t, err)
input = graphQLInput{
OperationName: "channels",
Query: query,
Variables: map[string]any{
"includeDeleted": false,
},
}
resp, err = th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.Channels, 8)
input = graphQLInput{
OperationName: "channels",
Query: query,
Variables: map[string]any{
"includeDeleted": true,
"lastDeleteAt": float64(model.GetMillis()),
},
}
resp, err = th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.Channels, 8)
input = graphQLInput{
OperationName: "channels",
Query: query,
Variables: map[string]any{
"includeDeleted": true,
"lastDeleteAt": float64(model.GetMillis()),
"first": 5,
},
}
resp, err = th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.Channels, 5)
})
}
func TestGetPrettyDNForUsers(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
t.Run("nickname_full_name", func(t *testing.T) {
users := []*model.User{
{
Id: "user1",
Nickname: "nick1",
Username: "user1",
FirstName: "first1",
LastName: "last1",
},
{
Id: "user2",
Nickname: "nick2",
Username: "user2",
FirstName: "first2",
LastName: "last2",
},
}
assert.Equal(t, "nick2", getPrettyDNForUsers("nickname_full_name", users, "user1", map[string]string{}))
users = []*model.User{
{
Id: "user1",
Username: "user1",
FirstName: "first1",
LastName: "last1",
},
{
Id: "user2",
Username: "user2",
FirstName: "first2",
LastName: "last2",
},
}
assert.Equal(t, "first2 last2", getPrettyDNForUsers("nickname_full_name", users, "user1", map[string]string{}))
})
t.Run("full_name", func(t *testing.T) {
users := []*model.User{
{
Id: "user1",
Nickname: "nick1",
Username: "user1",
FirstName: "first1",
LastName: "last1",
},
{
Id: "user2",
Nickname: "nick2",
Username: "user2",
FirstName: "first2",
LastName: "last2",
},
}
assert.Equal(t, "first2 last2", getPrettyDNForUsers("full_name", users, "user1", map[string]string{}))
users = []*model.User{
{
Id: "user1",
Username: "user1",
},
{
Id: "user2",
Username: "user2",
},
}
assert.Equal(t, "user2", getPrettyDNForUsers("full_name", users, "user1", map[string]string{}))
})
t.Run("username", func(t *testing.T) {
users := []*model.User{
{
Id: "user1",
Nickname: "nick1",
Username: "user1",
FirstName: "first1",
LastName: "last1",
},
{
Id: "user2",
Nickname: "nick2",
Username: "user2",
FirstName: "first2",
LastName: "last2",
},
}
assert.Equal(t, "user2", getPrettyDNForUsers("username", users, "user1", map[string]string{}))
})
t.Run("cache", func(t *testing.T) {
users := []*model.User{
{
Id: "user1",
Nickname: "nick1",
Username: "user1",
FirstName: "first1",
LastName: "last1",
},
{
Id: "user2",
Nickname: "nick2",
Username: "user2",
FirstName: "first2",
LastName: "last2",
},
}
cache := map[string]string{}
assert.Equal(t, "first2 last2", getPrettyDNForUsers("full_name", users, "user1", cache))
cache["user2"] = "teststring!!"
assert.Equal(t, "teststring!!", getPrettyDNForUsers("full_name", users, "user1", cache))
})
}
func TestChannelCursor(t *testing.T) {
ch := channel{
Channel: model.Channel{Id: "testid"},
}
cur := ch.Cursor()
id, ok := parseChannelCursor(*cur)
require.True(t, ok)
assert.Equal(t, ch.Id, id)
}

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

@@ -1,142 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"context"
"encoding/json"
"os"
"sort"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost/server/public/model"
)
func TestGraphQLSidebarCategories(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
th := Setup(t).InitBasic()
defer th.TearDown()
var q struct {
SidebarCategories []struct {
ID string `json:"id"`
DisplayName string `json:"displayName"`
Sorting model.SidebarCategorySorting `json:"sorting"`
ChannelIDs []string `json:"channelIds"`
TeamID string `json:"teamId"`
SortOrder int64 `json:"sortOrder"`
} `json:"sidebarCategories"`
}
input := graphQLInput{
OperationName: "sidebarCategories",
Query: `
query sidebarCategories($userId: String = "", $teamId: String = "", $excludeTeam: Boolean = false) {
sidebarCategories(userId: $userId, teamId: $teamId, excludeTeam: $excludeTeam) {
id
displayName
sorting
channelIds
sortOrder
}
}
`,
Variables: map[string]any{
"userId": "me",
"teamId": th.BasicTeam.Id,
},
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.SidebarCategories, 3)
categories, _, err := th.Client.GetSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
sort.Slice(q.SidebarCategories, func(i, j int) bool {
return q.SidebarCategories[i].ID < q.SidebarCategories[j].ID
})
sort.Slice(categories.Categories, func(i, j int) bool {
return categories.Categories[i].Id < categories.Categories[j].Id
})
for i := range categories.Categories {
assert.Equal(t, categories.Categories[i].Id, q.SidebarCategories[i].ID)
assert.Equal(t, categories.Categories[i].DisplayName, q.SidebarCategories[i].DisplayName)
assert.Equal(t, categories.Categories[i].Sorting, q.SidebarCategories[i].Sorting)
assert.Equal(t, categories.Categories[i].ChannelIds(), q.SidebarCategories[i].ChannelIDs)
assert.Equal(t, categories.Categories[i].SortOrder, q.SidebarCategories[i].SortOrder)
}
input = graphQLInput{
OperationName: "sidebarCategories",
Query: `
query sidebarCategories($userId: String = "", $teamId: String = "", $excludeTeam: Boolean = false) {
sidebarCategories(userId: $userId, teamId: $teamId, excludeTeam: $excludeTeam) {
id
displayName
sorting
channelIds
sortOrder
}
}
`,
Variables: map[string]any{
"userId": "me",
"teamId": th.BasicTeam.Id,
"excludeTeam": true,
},
}
resp, err = th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.SidebarCategories, 0)
// Adding a new team
myTeam := th.CreateTeam()
ch1 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypeOpen, myTeam.Id)
ch2 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypePrivate, myTeam.Id)
th.LinkUserToTeam(th.BasicUser, myTeam)
th.App.AddUserToChannel(th.Context, th.BasicUser, ch1, false)
th.App.AddUserToChannel(th.Context, th.BasicUser, ch2, false)
input = graphQLInput{
OperationName: "sidebarCategories",
Query: `
query sidebarCategories($userId: String = "", $teamId: String = "", $excludeTeam: Boolean = false) {
sidebarCategories(userId: $userId, teamId: $teamId, excludeTeam: $excludeTeam) {
id
displayName
sorting
channelIds
teamId
sortOrder
}
}
`,
Variables: map[string]any{
"userId": "me",
"teamId": th.BasicTeam.Id,
"excludeTeam": true,
},
}
resp, err = th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.SidebarCategories, 3)
for _, cat := range q.SidebarCategories {
assert.Equal(t, myTeam.Id, cat.TeamID)
}
}

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

@@ -1,95 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"context"
"fmt"
"github.com/graph-gophers/dataloader/v6"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/v8/channels/web"
)
func getGraphQLTeam(ctx context.Context, id string) (*model.Team, error) {
loader, err := getTeamsLoader(ctx)
if err != nil {
return nil, err
}
thunk := loader.Load(ctx, dataloader.StringKey(id))
result, err := thunk()
if err != nil {
return nil, err
}
team := result.(*model.Team)
return team, nil
}
func graphQLTeamsLoader(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
stringKeys := keys.Keys()
result := make([]*dataloader.Result, len(stringKeys))
c, err := getCtx(ctx)
if err != nil {
for i := range result {
result[i] = &dataloader.Result{Error: err}
}
return result
}
teams, err := getGraphQLTeams(c, stringKeys)
if err != nil {
for i := range result {
result[i] = &dataloader.Result{Error: err}
}
return result
}
for i, ch := range teams {
result[i] = &dataloader.Result{Data: ch}
}
return result
}
func getGraphQLTeams(c *web.Context, teamIDs []string) ([]*model.Team, error) {
teams, appErr := c.App.GetTeams(teamIDs)
if appErr != nil {
return nil, appErr
}
if len(teams) != len(teamIDs) {
return nil, fmt.Errorf("all teams were not found. Requested %d; Found %d", len(teamIDs), len(teams))
}
var teamsToCheck []string
for _, team := range teams {
if !team.AllowOpenInvite || team.Type != model.TeamOpen {
teamsToCheck = append(teamsToCheck, team.Id)
}
}
if !c.App.SessionHasPermissionToTeams(c.AppContext, *c.AppContext.Session(), teamsToCheck, model.PermissionViewTeam) {
c.SetPermissionError(model.PermissionViewTeam)
return nil, c.Err
}
for i, team := range teams {
teams[i] = c.App.SanitizeTeam(*c.AppContext.Session(), team)
}
// The teams need to be in the exact same order as the input slice.
tmp := make(map[string]*model.Team, len(teams))
for _, ch := range teams {
tmp[ch.Id] = ch
}
// We reuse the same slice and just rewrite the teams.
for i, id := range teamIDs {
teams[i] = tmp[id]
}
return teams, nil
}

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

@@ -1,50 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"context"
"strings"
"github.com/graph-gophers/dataloader/v6"
"github.com/mattermost/mattermost/server/public/model"
)
// teamMember is an internal graphQL wrapper struct to add resolver methods.
type teamMember struct {
model.TeamMember
}
// match with api4.getTeam
func (tm *teamMember) Team(ctx context.Context) (*model.Team, error) {
return getGraphQLTeam(ctx, tm.TeamId)
}
// match with api4.getUser
func (tm *teamMember) User(ctx context.Context) (*user, error) {
return getGraphQLUser(ctx, tm.UserId)
}
// match with api4.getRolesByNames
func (tm *teamMember) Roles_(ctx context.Context) ([]*model.Role, error) {
loader, err := getRolesLoader(ctx)
if err != nil {
return nil, err
}
thunk := loader.LoadMany(ctx, dataloader.NewKeysFromStrings(strings.Fields(tm.Roles)))
results, errs := thunk()
// All errors are the same. We just return the first one.
if len(errs) > 0 && errs[0] != nil {
return nil, err
}
roles := make([]*model.Role, len(results))
for i, res := range results {
roles[i] = res.(*model.Role)
}
return roles, nil
}

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

@@ -1,413 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"context"
"encoding/json"
"os"
"sort"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost/server/public/model"
)
func TestGraphQLTeamMembers(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
th := Setup(t).InitBasic()
defer th.TearDown()
var q struct {
TeamMembers []struct {
User struct {
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
NickName string `json:"nickname"`
} `json:"user"`
Team struct {
ID string `json:"id"`
DisplayName string `json:"displayName"`
Name string `json:"name"`
CreateAt float64 `json:"createAt"`
DeleteAt float64 `json:"deleteAt"`
SchemeId *string `json:"schemeId"`
PolicyId *string `json:"policyId"`
CloudLimitsArchived bool `json:"cloudLimitsArchived"`
} `json:"team"`
Roles []struct {
ID string `json:"id"`
Name string `json:"Name"`
Permissions []string `json:"permissions"`
SchemeManaged bool `json:"schemeManaged"`
BuiltIn bool `json:"builtIn"`
} `json:"roles"`
DeleteAt float64 `json:"deleteAt"`
SchemeGuest bool `json:"schemeGuest"`
SchemeUser bool `json:"schemeUser"`
SchemeAdmin bool `json:"schemeAdmin"`
} `json:"teamMembers"`
}
t.Run("User", func(t *testing.T) {
input := graphQLInput{
OperationName: "teamMembers",
Query: `
query teamMembers($userId: String = "", $teamId: String = "") {
teamMembers(userId: $userId, teamId: $teamId) {
team {
id
displayName
}
user {
id
username
email
firstName
lastName
}
roles {
id
name
}
schemeGuest
schemeUser
schemeAdmin
}
}
`,
Variables: map[string]any{
"userId": "me",
},
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.TeamMembers, 1)
tm := q.TeamMembers[0]
assert.Equal(t, th.BasicTeam.Id, tm.Team.ID)
assert.Equal(t, th.BasicTeam.DisplayName, tm.Team.DisplayName)
assert.Equal(t, th.BasicUser.Id, tm.User.ID)
assert.Equal(t, th.BasicUser.Username, tm.User.Username)
assert.Equal(t, th.BasicUser.Email, tm.User.Email)
assert.Equal(t, th.BasicUser.FirstName, tm.User.FirstName)
assert.Equal(t, th.BasicUser.LastName, tm.User.LastName)
require.Len(t, tm.Roles, 1)
assert.NotEmpty(t, tm.Roles[0].ID)
assert.Equal(t, "team_user", tm.Roles[0].Name)
assert.False(t, tm.SchemeGuest)
assert.True(t, tm.SchemeUser)
assert.False(t, tm.SchemeAdmin)
})
t.Run("User+Team", func(t *testing.T) {
input := graphQLInput{
OperationName: "teamMembers",
Query: `
query teamMembers($userId: String = "", $teamId: String = "") {
teamMembers(userId: $userId, teamId: $teamId) {
team {
id
displayName
name
createAt
deleteAt
schemeId
policyId
cloudLimitsArchived
}
user {
id
username
email
firstName
lastName
}
roles {
id
name
}
}
}
`,
Variables: map[string]any{
"userId": "me",
"teamId": th.BasicTeam.Id,
},
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.TeamMembers, 1)
tm := q.TeamMembers[0]
assert.Equal(t, th.BasicTeam.Id, tm.Team.ID)
assert.Equal(t, th.BasicTeam.DisplayName, tm.Team.DisplayName)
assert.Equal(t, th.BasicTeam.Name, tm.Team.Name)
assert.Equal(t, th.BasicTeam.CreateAt_(), tm.Team.CreateAt)
assert.Equal(t, th.BasicTeam.DeleteAt_(), tm.Team.DeleteAt)
assert.Equal(t, th.BasicTeam.SchemeId, tm.Team.SchemeId)
assert.Equal(t, th.BasicTeam.PolicyID, tm.Team.PolicyId)
assert.Equal(t, th.BasicTeam.CloudLimitsArchived, tm.Team.CloudLimitsArchived)
assert.Equal(t, th.BasicUser.Id, tm.User.ID)
assert.Equal(t, th.BasicUser.Username, tm.User.Username)
assert.Equal(t, th.BasicUser.Email, tm.User.Email)
assert.Equal(t, th.BasicUser.FirstName, tm.User.FirstName)
assert.Equal(t, th.BasicUser.LastName, tm.User.LastName)
require.Len(t, tm.Roles, 1)
assert.NotEmpty(t, tm.Roles[0].ID)
assert.Equal(t, "team_user", tm.Roles[0].Name)
})
t.Run("NewTeam", func(t *testing.T) {
// Adding another team with more channels (public and private)
myTeam := th.CreateTeam()
ch1 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypeOpen, myTeam.Id)
ch2 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypePrivate, myTeam.Id)
th.LinkUserToTeam(th.BasicUser, myTeam)
th.App.AddUserToChannel(th.Context, th.BasicUser, ch1, false)
th.App.AddUserToChannel(th.Context, th.BasicUser, ch2, false)
input := graphQLInput{
OperationName: "teamMembers",
Query: `
query teamMembers($userId: String = "", $teamId: String = "") {
teamMembers(userId: $userId, teamId: $teamId) {
team {
id
displayName
}
roles {
id
name
}
}
}
`,
Variables: map[string]any{
"userId": "me",
},
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.TeamMembers, 2)
sort.Slice(q.TeamMembers, func(i, j int) bool {
return q.TeamMembers[i].Team.ID < q.TeamMembers[j].Team.ID
})
expectedTeams := []*model.Team{th.BasicTeam, myTeam}
sort.Slice(expectedTeams, func(i, j int) bool {
return expectedTeams[i].Id < expectedTeams[j].Id
})
for i := range q.TeamMembers {
tm := q.TeamMembers[i]
if tm.Team.ID == myTeam.Id {
require.Len(t, tm.Roles, 2)
sort.Slice(tm.Roles, func(i, j int) bool {
return tm.Roles[i].Name < tm.Roles[j].Name
})
assert.Equal(t, "team_admin", tm.Roles[0].Name)
assert.Equal(t, "team_user", tm.Roles[1].Name)
} else {
require.Len(t, tm.Roles, 1)
assert.NotEmpty(t, tm.Roles[0].ID)
assert.Equal(t, "team_user", tm.Roles[0].Name)
}
expectedTeams[i].Id = tm.Team.ID
expectedTeams[i].DisplayName = tm.Team.DisplayName
}
// Negate team
input = graphQLInput{
OperationName: "teamMembers",
Query: `
query teamMembers($userId: String = "", $teamId: String = "") {
teamMembers(userId: $userId, teamId: $teamId, excludeTeam: true) {
team {
id
displayName
}
}
}
`,
Variables: map[string]any{
"userId": "me",
"teamId": th.BasicTeam.Id,
},
}
resp, err = th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.TeamMembers, 1)
input = graphQLInput{
OperationName: "teamMembers",
Query: `
query teamMembers($userId: String = "", $teamId: String = "") {
teamMembers(userId: $userId, teamId: $teamId) {
team {
id
displayName
}
}
}
`,
Variables: map[string]any{
"userId": "me",
},
}
// Removing from a team and ensuring we get the right response.
th.UnlinkUserFromTeam(th.BasicUser, myTeam)
resp, err = th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.TeamMembers, 1)
})
}
func TestGraphQLTeamMembersAsGuest(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
th := Setup(t)
id := model.NewId()
team := &model.Team{
DisplayName: "dn_" + id,
Name: GenerateTestTeamName(),
Email: th.GenerateTestEmail(),
Type: model.TeamOpen,
AllowOpenInvite: true,
}
var err error
team, _, err = th.Client.CreateTeam(context.Background(), team)
require.NoError(t, err)
th.BasicTeam = team
th.BasicChannel = th.CreatePublicChannel()
th.LinkUserToTeam(th.BasicUser, th.BasicTeam)
th.App.AddUserToChannel(th.Context, th.BasicUser, th.BasicChannel, false)
th.LoginBasic()
defer th.TearDown()
require.Nil(t, th.App.DemoteUserToGuest(th.Context, th.BasicUser))
var q struct {
TeamMembers []struct {
User struct {
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
NickName string `json:"nickname"`
} `json:"user"`
Team struct {
ID string `json:"id"`
DisplayName string `json:"displayName"`
Name string `json:"name"`
CreateAt float64 `json:"createAt"`
DeleteAt float64 `json:"deleteAt"`
SchemeId *string `json:"schemeId"`
PolicyId *string `json:"policyId"`
CloudLimitsArchived bool `json:"cloudLimitsArchived"`
} `json:"team"`
Roles []struct {
ID string `json:"id"`
Name string `json:"Name"`
Permissions []string `json:"permissions"`
SchemeManaged bool `json:"schemeManaged"`
BuiltIn bool `json:"builtIn"`
} `json:"roles"`
DeleteAt float64 `json:"deleteAt"`
SchemeGuest bool `json:"schemeGuest"`
SchemeUser bool `json:"schemeUser"`
SchemeAdmin bool `json:"schemeAdmin"`
} `json:"teamMembers"`
}
t.Run("User", func(t *testing.T) {
input := graphQLInput{
OperationName: "teamMembers",
Query: `
query teamMembers($userId: String = "", $teamId: String = "") {
teamMembers(userId: $userId, teamId: $teamId) {
team {
id
displayName
}
user {
id
username
email
firstName
lastName
}
roles {
id
name
}
schemeGuest
schemeUser
schemeAdmin
}
}
`,
Variables: map[string]any{
"userId": "me",
},
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.TeamMembers, 1)
tm := q.TeamMembers[0]
assert.Equal(t, th.BasicTeam.Id, tm.Team.ID)
assert.Equal(t, th.BasicTeam.DisplayName, tm.Team.DisplayName)
assert.Equal(t, th.BasicUser.Id, tm.User.ID)
assert.Equal(t, th.BasicUser.Username, tm.User.Username)
assert.Equal(t, th.BasicUser.Email, tm.User.Email)
assert.Equal(t, th.BasicUser.FirstName, tm.User.FirstName)
assert.Equal(t, th.BasicUser.LastName, tm.User.LastName)
require.Len(t, tm.Roles, 1)
assert.NotEmpty(t, tm.Roles[0].ID)
assert.Equal(t, "team_guest", tm.Roles[0].Name)
assert.True(t, tm.SchemeGuest)
assert.False(t, tm.SchemeUser)
assert.False(t, tm.SchemeAdmin)
})
}

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

@@ -1,229 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"context"
"encoding/json"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost/server/public/model"
)
func TestGraphQLConfig(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
th := Setup(t)
th.LoginBasicWithGraphQL()
defer th.TearDown()
var q struct {
Config map[string]string `json:"config"`
}
input := graphQLInput{
OperationName: "config",
Query: `
query config {
config
}
`,
}
cfg, _, err := th.Client.GetOldClientConfig(context.Background(), "")
require.NoError(t, err)
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Equal(t, cfg, q.Config)
}
func TestGraphQLLicense(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
th := Setup(t)
th.LoginBasicWithGraphQL()
defer th.TearDown()
var q struct {
License map[string]string `json:"license"`
}
input := graphQLInput{
OperationName: "license",
Query: `
query license {
license
}
`,
}
cfg, _, err := th.Client.GetOldClientLicense(context.Background(), "")
require.NoError(t, err)
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Equal(t, cfg, q.License)
}
func TestGraphQLChannelsLeft(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
th := Setup(t).InitBasic()
defer th.TearDown()
var q struct {
ChannelsLeft []string `json:"channelsLeft"`
}
t.Run("NotLeft", func(t *testing.T) {
input := graphQLInput{
OperationName: "channelsLeft",
Query: `
query channelsLeft($userId: String = "me", $since: Float = 0.0) {
channelsLeft(userId: $userId, since: $since)
}
`,
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.ChannelsLeft, 0)
})
t.Run("Left", func(t *testing.T) {
_, err := th.Client.RemoveUserFromChannel(context.Background(), th.BasicChannel.Id, th.BasicUser.Id)
require.NoError(t, err)
input := graphQLInput{
OperationName: "channelsLeft",
Query: `
query channelsLeft($userId: String = "me", $since: Float = 0.0) {
channelsLeft(userId: $userId, since: $since)
}
`,
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.ChannelsLeft, 1)
})
t.Run("LeftAfterTime", func(t *testing.T) {
input := graphQLInput{
OperationName: "channelsLeft",
Query: `
query channelsLeft($userId: String = "me", $since: Float = 0.0) {
channelsLeft(userId: $userId, since: $since)
}
`,
Variables: map[string]any{
"since": model.GetMillis(),
},
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.ChannelsLeft, 0)
})
}
func TestGraphQLRolesLoader(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
th := Setup(t).InitBasic()
defer th.TearDown()
var q struct {
User struct {
ID string `json:"id"`
Roles []struct {
ID string `json:"id"`
Name string `json:"Name"`
} `json:"roles"`
} `json:"user"`
ChannelMembers []struct {
MsgCount float64 `json:"msgCount"`
Roles []struct {
ID string `json:"id"`
Name string `json:"Name"`
} `json:"roles"`
} `json:"channelMembers"`
TeamMembers []struct {
SchemeUser bool `json:"schemeUser"`
Roles []struct {
ID string `json:"id"`
Name string `json:"Name"`
} `json:"roles"`
}
}
input := graphQLInput{
OperationName: "channelMembers",
Query: `
query channelMembers {
user(id: "me") {
id
username
roles {
id
name
}
}
channelMembers(userId: "me") {
msgCount
roles {
id
name
}
}
teamMembers(userId: "me") {
schemeUser
roles {
id
name
}
}
}
`,
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
require.Len(t, q.User.Roles, 1)
assert.Equal(t, "system_user", q.User.Roles[0].Name)
require.Len(t, q.ChannelMembers, 5)
for _, cm := range q.ChannelMembers {
require.Len(t, cm.Roles, 1)
assert.Equal(t, "channel_user", cm.Roles[0].Name)
}
require.Len(t, q.TeamMembers, 1)
for _, tm := range q.TeamMembers {
require.Len(t, tm.Roles, 1)
assert.Equal(t, "team_user", tm.Roles[0].Name)
}
}

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

@@ -1,222 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"context"
"net/http"
"github.com/graph-gophers/dataloader/v6"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/v8/channels/web"
)
// user is an internal graphQL wrapper struct to add resolver methods.
type user struct {
model.User
}
// match with api4.getUser
func getGraphQLUser(ctx context.Context, id string) (*user, error) {
c, err := getCtx(ctx)
if err != nil {
return nil, err
}
if id == model.Me {
id = c.AppContext.Session().UserId
}
if !model.IsValidId(id) {
return nil, web.NewInvalidParamError("user_id")
}
loader, err := getUsersLoader(ctx)
if err != nil {
return nil, err
}
thunk := loader.Load(ctx, dataloader.StringKey(id))
result, err := thunk()
if err != nil {
return nil, err
}
usr := result.(*model.User)
if c.IsSystemAdmin() || c.AppContext.Session().UserId == usr.Id {
userTermsOfService, appErr := c.App.GetUserTermsOfService(usr.Id)
if appErr != nil && appErr.StatusCode != http.StatusNotFound {
return nil, appErr
}
if userTermsOfService != nil {
usr.TermsOfServiceId = userTermsOfService.TermsOfServiceId
usr.TermsOfServiceCreateAt = userTermsOfService.CreateAt
}
}
c.App.Srv().Platform().UpdateLastActivityAtIfNeeded(*c.AppContext.Session())
return &user{*usr}, nil
}
// match with api4.getRolesByNames
func (u *user) Roles(ctx context.Context) ([]*model.Role, error) {
roleNames := u.GetRoles()
if len(roleNames) == 0 {
return nil, nil
}
loader, err := getRolesLoader(ctx)
if err != nil {
return nil, err
}
thunk := loader.LoadMany(ctx, dataloader.NewKeysFromStrings(roleNames))
results, errs := thunk()
// All errors are the same. We just return the first one.
if len(errs) > 0 && errs[0] != nil {
return nil, err
}
roles := make([]*model.Role, len(results))
for i, res := range results {
roles[i] = res.(*model.Role)
}
return roles, nil
}
// match with api4.getPreferences
func (u *user) Preferences(ctx context.Context) ([]model.Preference, error) {
c, err := getCtx(ctx)
if err != nil {
return nil, err
}
if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), u.Id) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return nil, c.Err
}
preferences, appErr := c.App.GetPreferencesForUser(u.Id)
if appErr != nil {
return nil, appErr
}
return preferences, nil
}
// match with api4.getUserStatus
func (u *user) Status(ctx context.Context) (*model.Status, error) {
c, err := getCtx(ctx)
if err != nil {
return nil, err
}
statuses, appErr := c.App.GetUserStatusesByIds([]string{u.Id})
if appErr != nil {
return nil, appErr
}
if len(statuses) == 0 {
return nil, model.NewAppError("UserStatus", "api.status.user_not_found.app_error", nil, "", http.StatusNotFound)
}
return statuses[0], nil
}
// match with api4.getSessions
func (u *user) Sessions(ctx context.Context) ([]*model.Session, error) {
c, err := getCtx(ctx)
if err != nil {
return nil, err
}
if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), u.Id) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return nil, c.Err
}
sessions, appErr := c.App.GetSessions(c.AppContext, u.Id)
if appErr != nil {
return nil, appErr
}
for _, session := range sessions {
session.Sanitize()
}
return sessions, nil
}
func graphQLUsersLoader(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
stringKeys := keys.Keys()
result := make([]*dataloader.Result, len(stringKeys))
c, err := getCtx(ctx)
if err != nil {
for i := range result {
result[i] = &dataloader.Result{Error: err}
}
return result
}
users, err := getGraphQLUsers(c, stringKeys)
if err != nil {
for i := range result {
result[i] = &dataloader.Result{Error: err}
}
return result
}
for i, user := range users {
result[i] = &dataloader.Result{Data: user}
}
return result
}
func getGraphQLUsers(c *web.Context, userIDs []string) ([]*model.User, error) {
// Usually this will be called only for one user
// and cached for the rest of the query. So it's not an issue
// to run this in a loop.
for _, id := range userIDs {
canSee, appErr := c.App.UserCanSeeOtherUser(c.AppContext, c.AppContext.Session().UserId, id)
if appErr != nil || !canSee {
c.SetPermissionError(model.PermissionViewMembers)
return nil, c.Err
}
}
users, appErr := c.App.GetUsers(userIDs)
if appErr != nil {
return nil, appErr
}
// Same as earlier, we want to pre-compute this only once
// because otherwise the resolvers run in multiple goroutines
// and *User.Sanitize causes a race, and we want to avoid
// deep-copying every user in all goroutines.
for _, user := range users {
if c.AppContext.Session().UserId == user.Id {
user.Sanitize(map[string]bool{})
} else {
c.App.SanitizeProfile(user, c.IsSystemAdmin())
}
}
// The users need to be in the exact same order as the input slice.
tmp := make(map[string]*model.User)
for _, u := range users {
tmp[u.Id] = u
}
// We reuse the same slice and just rewrite the roles.
for i, uID := range userIDs {
users[i] = tmp[uID]
}
return users, nil
}

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

@@ -1,244 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"context"
"encoding/json"
"os"
"sort"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost/server/public/model"
)
func TestGraphQLUser(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
th := Setup(t).InitBasic()
defer th.TearDown()
var q struct {
User struct {
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
NickName string `json:"nickname"`
IsBot bool `json:"isBot"`
IsSystemAdmin bool `json:"isSystemAdmin"`
CreateAt float64 `json:"createAt"`
DeleteAt float64 `json:"deleteAt"`
UpdateAt float64 `json:"updateAt"`
AuthData *string `json:"authData"`
EmailVerified bool `json:"emailVerified"`
CustomStatus struct {
Emoji string `json:"emoji"`
Text string `json:"text"`
Duration string `json:"duration"`
ExpiresAt time.Time `json:"expiresAt"`
} `json:"customStatus"`
Timezone model.StringMap `json:"timezone"`
Props model.StringMap `json:"props"`
NotifyProps model.StringMap `json:"notifyProps"`
Position string `json:"position"`
Roles []struct {
ID string `json:"id"`
Name string `json:"Name"`
Permissions []string `json:"permissions"`
SchemeManaged bool `json:"schemeManaged"`
BuiltIn bool `json:"builtIn"`
CreateAt float64 `json:"createAt"`
DeleteAt float64 `json:"deleteAt"`
UpdateAt float64 `json:"updateAt"`
} `json:"roles"`
Preferences []struct {
UserID string `json:"userId"`
Category string `json:"category"`
Name string `json:"name"`
Value string `json:"value"`
} `json:"preferences"`
Sessions []struct {
ID string `json:"id"`
CreateAt float64 `json:"createAt"`
LastActivityAt float64 `json:"lastActivityAt"`
DeviceId string `json:"deviceId"`
Roles string `json:"roles"`
} `json:"sessions"`
} `json:"user"`
}
t.Run("Basic", func(t *testing.T) {
input := graphQLInput{
OperationName: "user",
Query: `
query user($id: String = "me") {
user(id: $id) {
id
username
email
createAt
updateAt
deleteAt
firstName
lastName
emailVerified
isBot
isGuest
isSystemAdmin
timezone
props
notifyProps
roles {
id
name
createAt
updateAt
deleteAt
}
preferences {
name
value
}
sessions {
id
createAt
lastActivityAt
roles
}
}
}
`,
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Equal(t, th.BasicUser.Id, q.User.ID)
assert.Equal(t, th.BasicUser.Username, q.User.Username)
assert.Equal(t, th.BasicUser.Email, q.User.Email)
assert.Equal(t, th.BasicUser.FirstName, q.User.FirstName)
assert.Equal(t, th.BasicUser.IsBot, q.User.IsBot)
assert.Equal(t, float64(th.BasicUser.CreateAt), q.User.CreateAt)
assert.Equal(t, float64(th.BasicUser.DeleteAt), q.User.DeleteAt)
assert.NotZero(t, q.User.UpdateAt)
assert.Equal(t, th.BasicUser.IsSystemAdmin(), q.User.IsSystemAdmin)
assert.Equal(t, th.BasicUser.Timezone, q.User.Timezone)
assert.Equal(t, th.BasicUser.Props, q.User.Props)
assert.Equal(t, th.BasicUser.NotifyProps, q.User.NotifyProps)
roles, _, err := th.Client.GetRolesByNames(context.Background(), th.BasicUser.GetRoles())
require.NoError(t, err)
assert.Len(t, q.User.Roles, 1)
assert.Len(t, roles, 1)
assert.Equal(t, roles[0].Id, q.User.Roles[0].ID)
assert.Equal(t, roles[0].Name, q.User.Roles[0].Name)
assert.Equal(t, float64(roles[0].CreateAt), q.User.Roles[0].CreateAt)
assert.Equal(t, float64(roles[0].UpdateAt), q.User.Roles[0].UpdateAt)
assert.Equal(t, float64(roles[0].DeleteAt), q.User.Roles[0].DeleteAt)
prefs, _, err := th.Client.GetPreferences(context.Background(), th.BasicUser.Id)
require.NoError(t, err)
sort.Slice(prefs, func(i, j int) bool {
return prefs[i].Name < prefs[j].Name
})
sort.Slice(q.User.Preferences, func(i, j int) bool {
return q.User.Preferences[i].Name < q.User.Preferences[j].Name
})
for i := range prefs {
assert.Equal(t, q.User.Preferences[i].Name, prefs[i].Name)
assert.Equal(t, q.User.Preferences[i].Value, prefs[i].Value)
}
assert.Len(t, q.User.Sessions, 2)
now := float64(model.GetMillis())
for _, session := range q.User.Sessions {
assert.NotEmpty(t, session.ID)
assert.Less(t, session.CreateAt, now)
assert.Less(t, session.LastActivityAt, now)
assert.Equal(t, model.SystemUserRoleId, session.Roles)
}
})
t.Run("Update", func(t *testing.T) {
th.BasicUser.Props = map[string]string{"testpropkey": "testpropvalue"}
th.App.UpdateUser(th.Context, th.BasicUser, false)
input := graphQLInput{
OperationName: "user",
Query: `
query user($id: String = "me") {
user(id: $id) {
id
props
}
}
`,
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Equal(t, th.BasicUser.Props, q.User.Props)
})
t.Run("DifferentUser", func(t *testing.T) {
input := graphQLInput{
OperationName: "user",
Query: `
query user($id: String = "me") {
user(id: $id) {
id
props
}
}
`,
Variables: map[string]any{
"id": th.BasicUser2.Id,
},
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Equal(t, q.User.ID, th.BasicUser2.Id)
})
t.Run("BadUser", func(t *testing.T) {
id := model.NewId()
input := graphQLInput{
OperationName: "user",
Query: `
query user($id: String = "me") {
user(id: $id) {
id
props
}
}
`,
Variables: map[string]any{
"id": id,
},
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 1)
})
}

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

@@ -1,222 +0,0 @@
schema {
query: Query
}
type Query {
user(id: String!): User
config(): StringMap!
license(): StringMap!
teamMembers(userId: String!,
teamId: String = "",
excludeTeam: Boolean = false): [TeamMember]!
channels(userId: String!,
teamId: String = "",
includeDeleted: Boolean = false,
lastDeleteAt: Float = 0,
lastUpdateAt: Float = 0,
first: Int = 60,
after: String = ""): [Channel]!
channelsLeft(userId: String!,
since: Float!): [String!]!
channelMembers(userId: String!,
channelId: String = "",
teamId: String = "",
excludeTeam: Boolean = false,
first: Int = 60,
after: String = "",
lastUpdateAt: Float = 0): [ChannelMember]!
sidebarCategories(userId: String!,
teamId: String!,
excludeTeam: Boolean = false): [SidebarCategory]!
}
scalar ChannelType
scalar SidebarCategoryType
scalar SidebarCategorySorting
scalar StringMap
scalar StringInterface
scalar Time
type Channel {
id : String!
createAt : Float!
updateAt : Float!
deleteAt : Float!
type : ChannelType!
displayName: String!
prettyDisplayName: String!
name: String!
header: String!
purpose: String!
creatorId: String!
schemeId: String
team: Team
groupConstrained: Boolean
shared: Boolean
lastPostAt: Float!
totalMsgCount: Float!
totalMsgCountRoot: Float!
lastRootPostAt: Float!
extraUpdateAt: Float!
props: StringInterface!
policyId: String
cursor: String
}
type ChannelMember {
channel : Channel
user : User
roles : [Role]!
lastViewedAt : Float!
msgCount : Float!
mentionCount : Float!
urgentMentionCount: Float!
mentionCountRoot : Float!
msgCountRoot : Float!
notifyProps : StringMap!
lastUpdateAt : Float!
schemeGuest : Boolean!
schemeUser : Boolean!
schemeAdmin : Boolean!
explicitRoles : String!
cursor : String
}
# Deliberately omitting password, authData, mfaSecret.
type User {
id: String!
username: String!
email: String!
firstName: String!
lastName: String!
nickname: String!
emailVerified: Boolean!
isBot: Boolean!
isGuest: Boolean!
isSystemAdmin: Boolean!
createAt: Float!
updateAt: Float!
deleteAt: Float!
authService: String!
customStatus: CustomStatus
status: Status
props: StringMap!
notifyProps: StringMap!
lastPictureUpdate: Float!
lastPasswordUpdate: Float!
failedAttempts: Float!
locale: String!
timezone: StringMap!
position: String!
mfaActive: Boolean!
allowMarketing: Boolean!
remoteId: String
lastActivityAt: Float!
botDescription: String!
botLastIconUpdate: Float!
termsOfServiceId: String!
termsOfServiceCreateAt: Float!
disableWelcomeEmail: Boolean!
roles: [Role]!
preferences: [Preference!]!
sessions: [Session]!
}
type CustomStatus {
emoji: String!
text: String!
duration: String!
expiresAt: Time!
}
type Status {
status: String!
manual: Boolean!
lastActivityAt: Float!
activeChannel: String!
dndEndTime: Float!
}
type Role {
id: String!
name: String!
displayName: String!
description: String!
createAt: Float!
updateAt: Float!
deleteAt: Float!
permissions: [String!]!
schemeManaged: Boolean!
builtIn: Boolean!
}
type Preference {
userId: String!
category: String!
name: String!
value: String!
}
type Team {
id: String!
displayName : String!
name : String!
createAt : Float!
updateAt : Float!
deleteAt : Float!
description : String!
email : String!
type : String!
companyName : String!
allowedDomains : String!
inviteId : String!
lastTeamIconUpdate: Float!
groupConstrained: Boolean
allowOpenInvite: Boolean!
schemeId : String
policyId : String
cloudLimitsArchived: Boolean!
}
type TeamMember {
team: Team
user: User
roles: [Role]!
deleteAt: Float!
schemeGuest: Boolean!
schemeUser: Boolean!
schemeAdmin: Boolean!
explicitRoles: String!
}
type SidebarCategory {
id: String!
sorting: SidebarCategorySorting!
type: SidebarCategoryType!
displayName: String!
muted: Boolean!
collapsed: Boolean!
teamId: String!
channelIds: [String!]!
sortOrder: Float!
}
# Deliberately leaving out teamMembers.
type Session {
id: String!
token: String!
createAt: Float!
expiresAt: Float!
lastActivityAt: Float!
deviceId: String!
roles: String!
isOAuth: Boolean!
expiredNotify: Boolean!
props: StringMap!
local: Boolean!
}

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

@@ -626,7 +626,6 @@ type AppIface interface {
GetChannelsForScheme(scheme *model.Scheme, offset int, limit int) (model.ChannelList, *model.AppError) GetChannelsForScheme(scheme *model.Scheme, offset int, limit int) (model.ChannelList, *model.AppError)
GetChannelsForSchemePage(scheme *model.Scheme, page int, perPage int) (model.ChannelList, *model.AppError) GetChannelsForSchemePage(scheme *model.Scheme, page int, perPage int) (model.ChannelList, *model.AppError)
GetChannelsForTeamForUser(c request.CTX, teamID string, userID string, opts *model.ChannelSearchOpts) (model.ChannelList, *model.AppError) GetChannelsForTeamForUser(c request.CTX, teamID string, userID string, opts *model.ChannelSearchOpts) (model.ChannelList, *model.AppError)
GetChannelsForTeamForUserWithCursor(c request.CTX, teamID string, userID string, opts *model.ChannelSearchOpts, afterChannelID string) (model.ChannelList, *model.AppError)
GetChannelsForUser(c request.CTX, userID string, includeDeleted bool, lastDeleteAt, pageSize int, fromChannelID string) (model.ChannelList, *model.AppError) GetChannelsForUser(c request.CTX, userID string, includeDeleted bool, lastDeleteAt, pageSize int, fromChannelID string) (model.ChannelList, *model.AppError)
GetChannelsMemberCount(c request.CTX, channelIDs []string) (map[string]int64, *model.AppError) GetChannelsMemberCount(c request.CTX, channelIDs []string) (map[string]int64, *model.AppError)
GetChannelsUserNotIn(c request.CTX, teamID string, userID string, offset int, limit int) (model.ChannelList, *model.AppError) GetChannelsUserNotIn(c request.CTX, teamID string, userID string, offset int, limit int) (model.ChannelList, *model.AppError)

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

@@ -1938,14 +1938,6 @@ func (a *App) GetChannelsForTeamForUser(c request.CTX, teamID string, userID str
return a.Srv().getChannelsForTeamForUser(c, teamID, userID, opts) return a.Srv().getChannelsForTeamForUser(c, teamID, userID, opts)
} }
func (a *App) GetChannelsForTeamForUserWithCursor(c request.CTX, teamID string, userID string, opts *model.ChannelSearchOpts, afterChannelID string) (model.ChannelList, *model.AppError) {
list, err := a.Srv().Store().Channel().GetChannelsWithCursor(teamID, userID, opts, afterChannelID)
if err != nil {
return nil, model.NewAppError("GetChannelsForUser", "app.channel.get_channels.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return list, nil
}
func (a *App) GetChannelsForUser(c request.CTX, userID string, includeDeleted bool, lastDeleteAt, pageSize int, fromChannelID string) (model.ChannelList, *model.AppError) { func (a *App) GetChannelsForUser(c request.CTX, userID string, includeDeleted bool, lastDeleteAt, pageSize int, fromChannelID string) (model.ChannelList, *model.AppError) {
list, err := a.Srv().Store().Channel().GetChannelsByUser(userID, includeDeleted, lastDeleteAt, pageSize, fromChannelID) list, err := a.Srv().Store().Channel().GetChannelsByUser(userID, includeDeleted, lastDeleteAt, pageSize, fromChannelID)
if err != nil { if err != nil {

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

@@ -5718,28 +5718,6 @@ func (a *OpenTracingAppLayer) GetChannelsForTeamForUser(c request.CTX, teamID st
return resultVar0, resultVar1 return resultVar0, resultVar1
} }
func (a *OpenTracingAppLayer) GetChannelsForTeamForUserWithCursor(c request.CTX, teamID string, userID string, opts *model.ChannelSearchOpts, afterChannelID string) (model.ChannelList, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetChannelsForTeamForUserWithCursor")
a.ctx = newCtx
a.app.Srv().Store().SetContext(newCtx)
defer func() {
a.app.Srv().Store().SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
resultVar0, resultVar1 := a.app.GetChannelsForTeamForUserWithCursor(c, teamID, userID, opts, afterChannelID)
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
ext.Error.Set(span, true)
}
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetChannelsForUser(c request.CTX, userID string, includeDeleted bool, lastDeleteAt int, pageSize int, fromChannelID string) (model.ChannelList, *model.AppError) { func (a *OpenTracingAppLayer) GetChannelsForUser(c request.CTX, userID string, includeDeleted bool, lastDeleteAt int, pageSize int, fromChannelID string) (model.ChannelList, *model.AppError) {
origCtx := a.ctx origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetChannelsForUser") span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetChannelsForUser")

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

@@ -294,7 +294,7 @@ func generateLayer(name, templateFile string) ([]byte, error) {
switch param.Type { switch param.Type {
case "ChannelSearchOpts", "UserGetByIdsOpts", "ThreadMembershipOpts": case "ChannelSearchOpts", "UserGetByIdsOpts", "ThreadMembershipOpts":
paramsWithType = append(paramsWithType, fmt.Sprintf("%s store.%s", param.Name, param.Type)) paramsWithType = append(paramsWithType, fmt.Sprintf("%s store.%s", param.Name, param.Type))
case "*UserGetByIdsOpts", "*ChannelMemberGraphQLSearchOpts", "*SidebarCategorySearchOpts": case "*UserGetByIdsOpts", "*SidebarCategorySearchOpts":
paramsWithType = append(paramsWithType, fmt.Sprintf("%s *store.%s", param.Name, strings.TrimPrefix(param.Type, "*"))) paramsWithType = append(paramsWithType, fmt.Sprintf("%s *store.%s", param.Name, strings.TrimPrefix(param.Type, "*")))
default: default:
paramsWithType = append(paramsWithType, fmt.Sprintf("%s %s", param.Name, param.Type)) paramsWithType = append(paramsWithType, fmt.Sprintf("%s %s", param.Name, param.Type))
@@ -308,7 +308,7 @@ func generateLayer(name, templateFile string) ([]byte, error) {
switch param.Type { switch param.Type {
case "ChannelSearchOpts", "UserGetByIdsOpts", "ThreadMembershipOpts": case "ChannelSearchOpts", "UserGetByIdsOpts", "ThreadMembershipOpts":
paramsWithType = append(paramsWithType, fmt.Sprintf("%s store.%s", param.Name, param.Type)) paramsWithType = append(paramsWithType, fmt.Sprintf("%s store.%s", param.Name, param.Type))
case "*UserGetByIdsOpts", "*ChannelMemberGraphQLSearchOpts", "*SidebarCategorySearchOpts": case "*UserGetByIdsOpts", "*SidebarCategorySearchOpts":
paramsWithType = append(paramsWithType, fmt.Sprintf("%s *store.%s", param.Name, strings.TrimPrefix(param.Type, "*"))) paramsWithType = append(paramsWithType, fmt.Sprintf("%s *store.%s", param.Name, strings.TrimPrefix(param.Type, "*")))
default: default:
paramsWithType = append(paramsWithType, fmt.Sprintf("%s %s", param.Name, param.Type)) paramsWithType = append(paramsWithType, fmt.Sprintf("%s %s", param.Name, param.Type))

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

@@ -1333,24 +1333,6 @@ func (s *OpenTracingLayerChannelStore) GetChannelsMemberCount(channelIDs []strin
return result, err return result, err
} }
func (s *OpenTracingLayerChannelStore) GetChannelsWithCursor(teamId string, userId string, opts *model.ChannelSearchOpts, afterChannelID string) (model.ChannelList, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetChannelsWithCursor")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.ChannelStore.GetChannelsWithCursor(teamId, userId, opts, afterChannelID)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerChannelStore) GetChannelsWithTeamDataByIds(channelIds []string, includeDeleted bool) ([]*model.ChannelWithTeamData, error) { func (s *OpenTracingLayerChannelStore) GetChannelsWithTeamDataByIds(channelIds []string, includeDeleted bool) ([]*model.ChannelWithTeamData, error) {
origCtx := s.Root.Store.Context() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetChannelsWithTeamDataByIds") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetChannelsWithTeamDataByIds")
@@ -1652,24 +1634,6 @@ func (s *OpenTracingLayerChannelStore) GetMembersForUser(teamID string, userID s
return result, err return result, err
} }
func (s *OpenTracingLayerChannelStore) GetMembersForUserWithCursor(userID string, teamID string, opts *store.ChannelMemberGraphQLSearchOpts) (model.ChannelMembers, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetMembersForUserWithCursor")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.ChannelStore.GetMembersForUserWithCursor(userID, teamID, opts)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerChannelStore) GetMembersForUserWithPagination(userID string, page int, perPage int) (model.ChannelMembersWithTeamData, error) { func (s *OpenTracingLayerChannelStore) GetMembersForUserWithPagination(userID string, page int, perPage int) (model.ChannelMembersWithTeamData, error) {
origCtx := s.Root.Store.Context() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetMembersForUserWithPagination") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetMembersForUserWithPagination")

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

@@ -1480,27 +1480,6 @@ func (s *RetryLayerChannelStore) GetChannelsMemberCount(channelIDs []string) (ma
} }
func (s *RetryLayerChannelStore) GetChannelsWithCursor(teamId string, userId string, opts *model.ChannelSearchOpts, afterChannelID string) (model.ChannelList, error) {
tries := 0
for {
result, err := s.ChannelStore.GetChannelsWithCursor(teamId, userId, opts, afterChannelID)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerChannelStore) GetChannelsWithTeamDataByIds(channelIds []string, includeDeleted bool) ([]*model.ChannelWithTeamData, error) { func (s *RetryLayerChannelStore) GetChannelsWithTeamDataByIds(channelIds []string, includeDeleted bool) ([]*model.ChannelWithTeamData, error) {
tries := 0 tries := 0
@@ -1843,27 +1822,6 @@ func (s *RetryLayerChannelStore) GetMembersForUser(teamID string, userID string)
} }
func (s *RetryLayerChannelStore) GetMembersForUserWithCursor(userID string, teamID string, opts *store.ChannelMemberGraphQLSearchOpts) (model.ChannelMembers, error) {
tries := 0
for {
result, err := s.ChannelStore.GetMembersForUserWithCursor(userID, teamID, opts)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerChannelStore) GetMembersForUserWithPagination(userID string, page int, perPage int) (model.ChannelMembersWithTeamData, error) { func (s *RetryLayerChannelStore) GetMembersForUserWithPagination(userID string, page int, perPage int) (model.ChannelMembersWithTeamData, error) {
tries := 0 tries := 0

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

@@ -1073,66 +1073,6 @@ func (s SqlChannelStore) GetChannels(teamId string, userId string, opts *model.C
return channels, nil return channels, nil
} }
func (s SqlChannelStore) GetChannelsWithCursor(teamId string, userId string, opts *model.ChannelSearchOpts, afterChannelID string) (model.ChannelList, error) {
query := s.getQueryBuilder().
Select("ch.*").
From("Channels ch, ChannelMembers cm").
Where(
sq.And{
sq.Expr("ch.Id = cm.ChannelId"),
sq.Eq{"cm.UserId": userId},
},
).
OrderBy("ch.Id")
if opts.PerPage != nil {
// The limit is verified at the GraphQL layer.
query = query.Limit(uint64(*opts.PerPage))
}
if afterChannelID != "" {
query = query.Where(sq.Gt{"ch.Id": afterChannelID})
}
if teamId != "" {
query = query.Where(sq.Or{
sq.Eq{"ch.TeamId": teamId},
sq.Eq{"ch.TeamId": ""},
})
}
if opts.IncludeDeleted {
if opts.LastDeleteAt != 0 {
// We filter by non-archived, and archived >= a timestamp.
query = query.Where(sq.Or{
sq.Eq{"ch.DeleteAt": 0},
sq.GtOrEq{"ch.DeleteAt": opts.LastDeleteAt},
})
}
// If opts.LastDeleteAt is not set, we include everything. That means no filter is needed.
} else {
// Don't include archived channels.
query = query.Where(sq.Eq{"ch.DeleteAt": 0})
}
if opts.LastUpdateAt > 0 {
query = query.Where(sq.GtOrEq{"ch.UpdateAt": opts.LastUpdateAt})
}
channels := model.ChannelList{}
sql, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrapf(err, "getchannels_tosql")
}
err = s.GetReplicaX().Select(&channels, sql, args...)
if err != nil {
return nil, errors.Wrapf(err, "failed to get channels with TeamId=%s and UserId=%s", teamId, userId)
}
return channels, nil
}
func (s SqlChannelStore) GetChannelsByUser(userId string, includeDeleted bool, lastDeleteAt, pageSize int, fromChannelID string) (model.ChannelList, error) { func (s SqlChannelStore) GetChannelsByUser(userId string, includeDeleted bool, lastDeleteAt, pageSize int, fromChannelID string) (model.ChannelList, error) {
query := s.getQueryBuilder(). query := s.getQueryBuilder().
Select("Channels.*"). Select("Channels.*").
@@ -3050,86 +2990,6 @@ func (s SqlChannelStore) GetMembersForUser(teamID string, userID string) (model.
return dbMembers.ToModel(), nil return dbMembers.ToModel(), nil
} }
func (s SqlChannelStore) GetMembersForUserWithCursor(userID, teamID string, opts *store.ChannelMemberGraphQLSearchOpts) (model.ChannelMembers, error) {
query := s.getQueryBuilder().
Select(
"ChannelMembers.ChannelId",
"ChannelMembers.UserId",
"ChannelMembers.Roles",
"ChannelMembers.LastViewedAt",
"ChannelMembers.MsgCount",
"ChannelMembers.MentionCount",
"ChannelMembers.MentionCountRoot",
"COALESCE(ChannelMembers.UrgentMentionCount, 0) AS UrgentMentionCount",
"ChannelMembers.MsgCountRoot",
"ChannelMembers.NotifyProps",
"ChannelMembers.LastUpdateAt",
"ChannelMembers.SchemeUser",
"ChannelMembers.SchemeAdmin",
"ChannelMembers.SchemeGuest",
"TeamScheme.DefaultChannelGuestRole TeamSchemeDefaultGuestRole",
"TeamScheme.DefaultChannelUserRole TeamSchemeDefaultUserRole",
"TeamScheme.DefaultChannelAdminRole TeamSchemeDefaultAdminRole",
"ChannelScheme.DefaultChannelGuestRole ChannelSchemeDefaultGuestRole",
"ChannelScheme.DefaultChannelUserRole ChannelSchemeDefaultUserRole",
"ChannelScheme.DefaultChannelAdminRole ChannelSchemeDefaultAdminRole").
From("ChannelMembers").
InnerJoin("Channels ON ChannelMembers.ChannelId = Channels.Id").
LeftJoin("Schemes ChannelScheme ON Channels.SchemeId = ChannelScheme.Id").
LeftJoin("Teams ON Channels.TeamId = Teams.Id").
LeftJoin("Schemes TeamScheme ON Teams.SchemeId = TeamScheme.Id").
Where(sq.Eq{
"ChannelMembers.UserId": userID,
"Channels.DeleteAt": 0,
}).
OrderBy("ChannelId, UserId ASC").
// The limit is verified at the GraphQL layer.
Limit(uint64(opts.Limit))
if teamID != "" {
if opts.ExcludeTeam {
// Exclude this team and DM/GMs
query = query.Where(sq.And{
sq.NotEq{"Channels.TeamId": teamID},
sq.NotEq{"Channels.TeamId": ""},
})
} else {
// Include this team and DM/GMs
query = query.Where(sq.Or{
sq.Eq{"Channels.TeamId": teamID},
sq.Eq{"Channels.TeamId": ""},
})
}
}
if opts.AfterChannel != "" && opts.AfterUser != "" {
query = query.Where(sq.Or{
sq.Gt{"ChannelMembers.ChannelId": opts.AfterChannel},
sq.And{
sq.Eq{"ChannelMembers.ChannelId": opts.AfterChannel},
sq.Gt{"ChannelMembers.UserId": opts.AfterUser},
},
})
}
if opts.LastUpdateAt != 0 {
query = query.Where(sq.GtOrEq{"ChannelMembers.LastUpdateAt": opts.LastUpdateAt})
}
queryString, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrap(err, "getMembersForUserWithCursor_tosql")
}
dbMembers := channelMemberWithSchemeRolesList{}
err = s.GetReplicaX().Select(&dbMembers, queryString, args...)
if err != nil {
return nil, errors.Wrapf(err, "failed to find ChannelMembers data with userId=%s", userID)
}
return dbMembers.ToModel(), nil
}
func (s SqlChannelStore) GetMembersForUserWithPagination(userId string, page, perPage int) (model.ChannelMembersWithTeamData, error) { func (s SqlChannelStore) GetMembersForUserWithPagination(userId string, page, perPage int) (model.ChannelMembersWithTeamData, error) {
dbMembers := channelMemberWithTeamWithSchemeRolesList{} dbMembers := channelMemberWithTeamWithSchemeRolesList{}
offset := page * perPage offset := page * perPage

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

@@ -201,7 +201,6 @@ type ChannelStore interface {
GetDeletedByName(team_id string, name string) (*model.Channel, error) GetDeletedByName(team_id string, name string) (*model.Channel, error)
GetDeleted(team_id string, offset int, limit int, userID string) (model.ChannelList, error) GetDeleted(team_id string, offset int, limit int, userID string) (model.ChannelList, error)
GetChannels(teamID, userID string, opts *model.ChannelSearchOpts) (model.ChannelList, error) GetChannels(teamID, userID string, opts *model.ChannelSearchOpts) (model.ChannelList, error)
GetChannelsWithCursor(teamId string, userId string, opts *model.ChannelSearchOpts, afterChannelID string) (model.ChannelList, error)
GetChannelsByUser(userID string, includeDeleted bool, lastDeleteAt, pageSize int, fromChannelID string) (model.ChannelList, error) GetChannelsByUser(userID string, includeDeleted bool, lastDeleteAt, pageSize int, fromChannelID string) (model.ChannelList, error)
GetAllChannelMembersById(id string) ([]string, error) GetAllChannelMembersById(id string) ([]string, error)
GetAllChannels(page, perPage int, opts ChannelSearchOpts) (model.ChannelListWithTeamData, error) GetAllChannels(page, perPage int, opts ChannelSearchOpts) (model.ChannelListWithTeamData, error)
@@ -256,7 +255,6 @@ type ChannelStore interface {
GetMembersForUser(teamID string, userID string) (model.ChannelMembers, error) GetMembersForUser(teamID string, userID string) (model.ChannelMembers, error)
GetTeamMembersForChannel(channelID string) ([]string, error) GetTeamMembersForChannel(channelID string) ([]string, error)
GetMembersForUserWithPagination(userID string, page, perPage int) (model.ChannelMembersWithTeamData, error) GetMembersForUserWithPagination(userID string, page, perPage int) (model.ChannelMembersWithTeamData, error)
GetMembersForUserWithCursor(userID, teamID string, opts *ChannelMemberGraphQLSearchOpts) (model.ChannelMembers, error)
Autocomplete(userID, term string, includeDeleted, isGuest bool) (model.ChannelListWithTeamData, error) Autocomplete(userID, term string, includeDeleted, isGuest bool) (model.ChannelListWithTeamData, error)
AutocompleteInTeam(teamID, userID, term string, includeDeleted, isGuest bool) (model.ChannelList, error) AutocompleteInTeam(teamID, userID, term string, includeDeleted, isGuest bool) (model.ChannelList, error)
AutocompleteInTeamForSearch(teamID string, userID string, term string, includeDeleted bool) (model.ChannelList, error) AutocompleteInTeamForSearch(teamID string, userID string, term string, includeDeleted bool) (model.ChannelList, error)
@@ -1079,16 +1077,6 @@ type ThreadMembershipOpts struct {
UpdateParticipants bool UpdateParticipants bool
} }
// ChannelMemberGraphQLSearchOpts contains the options for a graphQL query
// to get the channel members.
type ChannelMemberGraphQLSearchOpts struct {
AfterChannel string
AfterUser string
Limit int
LastUpdateAt int
ExcludeTeam bool
}
// PostReminderMetadata contains some info needed to send // PostReminderMetadata contains some info needed to send
// the reminder message to the user. // the reminder message to the user.
type PostReminderMetadata struct { type PostReminderMetadata struct {

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

@@ -94,7 +94,6 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlStore) {
t.Run("RemoveMembers", func(t *testing.T) { testChannelRemoveMembers(t, ss) }) t.Run("RemoveMembers", func(t *testing.T) { testChannelRemoveMembers(t, ss) })
t.Run("ChannelDeleteMemberStore", func(t *testing.T) { testChannelDeleteMemberStore(t, ss) }) t.Run("ChannelDeleteMemberStore", func(t *testing.T) { testChannelDeleteMemberStore(t, ss) })
t.Run("GetChannels", func(t *testing.T) { testChannelStoreGetChannels(t, ss) }) t.Run("GetChannels", func(t *testing.T) { testChannelStoreGetChannels(t, ss) })
t.Run("GetChannelsWithCursor", func(t *testing.T) { testChannelStoreGetChannelsWithCursor(t, ss) })
t.Run("GetChannelsByUser", func(t *testing.T) { testChannelStoreGetChannelsByUser(t, ss) }) t.Run("GetChannelsByUser", func(t *testing.T) { testChannelStoreGetChannelsByUser(t, ss) })
t.Run("GetAllChannels", func(t *testing.T) { testChannelStoreGetAllChannels(t, ss, s) }) t.Run("GetAllChannels", func(t *testing.T) { testChannelStoreGetAllChannels(t, ss, s) })
t.Run("GetMoreChannels", func(t *testing.T) { testChannelStoreGetMoreChannels(t, ss) }) t.Run("GetMoreChannels", func(t *testing.T) { testChannelStoreGetMoreChannels(t, ss) })
@@ -103,7 +102,6 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlStore) {
t.Run("GetPublicChannelsByIdsForTeam", func(t *testing.T) { testChannelStoreGetPublicChannelsByIdsForTeam(t, ss) }) t.Run("GetPublicChannelsByIdsForTeam", func(t *testing.T) { testChannelStoreGetPublicChannelsByIdsForTeam(t, ss) })
t.Run("GetChannelCounts", func(t *testing.T) { testChannelStoreGetChannelCounts(t, ss) }) t.Run("GetChannelCounts", func(t *testing.T) { testChannelStoreGetChannelCounts(t, ss) })
t.Run("GetMembersForUser", func(t *testing.T) { testChannelStoreGetMembersForUser(t, ss) }) t.Run("GetMembersForUser", func(t *testing.T) { testChannelStoreGetMembersForUser(t, ss) })
t.Run("GetMembersForUserWithCursor", func(t *testing.T) { testChannelStoreGetMembersForUserWithCursor(t, ss) })
t.Run("GetMembersForUserWithPagination", func(t *testing.T) { testChannelStoreGetMembersForUserWithPagination(t, ss) }) t.Run("GetMembersForUserWithPagination", func(t *testing.T) { testChannelStoreGetMembersForUserWithPagination(t, ss) })
t.Run("CountPostsAfter", func(t *testing.T) { testCountPostsAfter(t, ss) }) t.Run("CountPostsAfter", func(t *testing.T) { testCountPostsAfter(t, ss) })
t.Run("CountUrgentPostsAfter", func(t *testing.T) { testCountUrgentPostsAfter(t, ss) }) t.Run("CountUrgentPostsAfter", func(t *testing.T) { testCountUrgentPostsAfter(t, ss) })
@@ -3607,163 +3605,6 @@ func testChannelStoreGetChannels(t *testing.T, ss store.Store) {
ss.Channel().InvalidateAllChannelMembersForUser(m1.UserId) ss.Channel().InvalidateAllChannelMembersForUser(m1.UserId)
} }
func testChannelStoreGetChannelsWithCursor(t *testing.T, ss store.Store) {
teamID := model.NewId()
o1 := &model.Channel{}
o1.TeamId = teamID
o1.DisplayName = "Channel1"
o1.Name = NewTestId()
o1.Type = model.ChannelTypeOpen
var nErr error
o1, nErr = ss.Channel().Save(o1, -1)
require.NoError(t, nErr)
o2 := model.Channel{}
o2.TeamId = teamID
o2.DisplayName = "Channel2"
o2.Name = NewTestId()
o2.Type = model.ChannelTypeOpen
_, nErr = ss.Channel().Save(&o2, -1)
require.NoError(t, nErr)
o3 := model.Channel{}
o3.TeamId = teamID
o3.DisplayName = "Channel3"
o3.Name = NewTestId()
o3.Type = model.ChannelTypeOpen
_, nErr = ss.Channel().Save(&o3, -1)
require.NoError(t, nErr)
m1 := model.ChannelMember{}
m1.ChannelId = o1.Id
m1.UserId = model.NewId()
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
_, err := ss.Channel().SaveMember(&m1)
require.NoError(t, err)
m2 := model.ChannelMember{}
m2.ChannelId = o1.Id
m2.UserId = model.NewId()
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
_, err = ss.Channel().SaveMember(&m2)
require.NoError(t, err)
m3 := model.ChannelMember{}
m3.ChannelId = o2.Id
m3.UserId = m1.UserId
m3.NotifyProps = model.GetDefaultChannelNotifyProps()
_, err = ss.Channel().SaveMember(&m3)
require.NoError(t, err)
m4 := model.ChannelMember{}
m4.ChannelId = o3.Id
m4.UserId = m1.UserId
m4.NotifyProps = model.GetDefaultChannelNotifyProps()
_, err = ss.Channel().SaveMember(&m4)
require.NoError(t, err)
list, nErr := ss.Channel().GetChannelsWithCursor(o1.TeamId, m1.UserId, &model.ChannelSearchOpts{
IncludeDeleted: false,
LastDeleteAt: 0,
PerPage: model.NewInt(2),
}, "")
require.NoError(t, nErr)
require.Len(t, list, 2)
require.Equal(t, teamID, list[0].TeamId, "incorrect teamID")
require.Equal(t, teamID, list[1].TeamId, "incorrect teamID")
list, nErr = ss.Channel().GetChannelsWithCursor(o1.TeamId, m1.UserId, &model.ChannelSearchOpts{
IncludeDeleted: false,
LastDeleteAt: 0,
PerPage: model.NewInt(2),
}, list[1].Id)
require.NoError(t, nErr)
require.Len(t, list, 1)
require.Equal(t, teamID, list[0].TeamId, "incorrect teamID")
// all channels should be returned
list, nErr = ss.Channel().GetChannelsWithCursor(o1.TeamId, m1.UserId, &model.ChannelSearchOpts{
IncludeDeleted: false,
LastDeleteAt: 0,
}, "")
require.NoError(t, nErr)
require.Len(t, list, 3)
// should return empty list
list, nErr = ss.Channel().GetChannelsWithCursor(o1.TeamId, m1.UserId, &model.ChannelSearchOpts{
IncludeDeleted: false,
LastDeleteAt: 0,
}, list[2].Id)
require.NoError(t, nErr)
require.Len(t, list, 0)
// Sleeping to guarantee that the
// UpdateAt is different.
// The proper way would be to set UpdateAt during channel creation itself,
// but the *Channel.PreSave method ignores any existing CreateAt value.
// TODO: check if using an existing CreateAt breaks anything.
time.Sleep(time.Millisecond)
now := model.GetMillis()
_, nErr = ss.Channel().Update(o1)
require.NoError(t, nErr)
list, nErr = ss.Channel().GetChannelsWithCursor(o1.TeamId, m1.UserId, &model.ChannelSearchOpts{
IncludeDeleted: false,
LastUpdateAt: int(now),
}, "")
require.NoError(t, nErr)
// should return 1
require.Len(t, list, 1)
nErr = ss.Channel().Delete(o2.Id, 10)
require.NoError(t, nErr)
nErr = ss.Channel().Delete(o3.Id, 20)
require.NoError(t, nErr)
// should return 1
list, nErr = ss.Channel().GetChannelsWithCursor(o1.TeamId, m1.UserId, &model.ChannelSearchOpts{
IncludeDeleted: false,
LastDeleteAt: 0,
}, "")
require.NoError(t, nErr)
require.Len(t, list, 1)
// Should return all
list, nErr = ss.Channel().GetChannelsWithCursor(o1.TeamId, m1.UserId, &model.ChannelSearchOpts{
IncludeDeleted: true,
LastDeleteAt: 0,
PerPage: model.NewInt(2),
}, "")
require.NoError(t, nErr)
require.Len(t, list, 2)
list, nErr = ss.Channel().GetChannelsWithCursor(o1.TeamId, m1.UserId, &model.ChannelSearchOpts{
IncludeDeleted: true,
LastDeleteAt: 0,
PerPage: model.NewInt(2),
}, list[1].Id)
require.NoError(t, nErr)
require.Len(t, list, 1)
// Should still return all
list, nErr = ss.Channel().GetChannelsWithCursor(o1.TeamId, m1.UserId, &model.ChannelSearchOpts{
IncludeDeleted: true,
LastDeleteAt: 10,
}, "")
require.NoError(t, nErr)
require.Len(t, list, 3)
// Should return 2
list, nErr = ss.Channel().GetChannelsWithCursor(o1.TeamId, m1.UserId, &model.ChannelSearchOpts{
IncludeDeleted: true,
LastDeleteAt: 20,
}, "")
require.NoError(t, nErr)
require.Len(t, list, 2)
}
func testChannelStoreGetChannelsByUser(t *testing.T, ss store.Store) { func testChannelStoreGetChannelsByUser(t *testing.T, ss store.Store) {
team := model.NewId() team := model.NewId()
team2 := model.NewId() team2 := model.NewId()
@@ -4562,183 +4403,6 @@ func testChannelStoreGetMembersForUser(t *testing.T, ss store.Store) {
}) })
} }
func testChannelStoreGetMembersForUserWithCursor(t *testing.T, ss store.Store) {
t1 := model.Team{}
t1.DisplayName = "Team1"
t1.Name = NewTestId()
t1.Email = MakeEmail()
t1.Type = model.TeamOpen
_, err := ss.Team().Save(&t1)
require.NoError(t, err)
t2 := model.Team{}
t2.DisplayName = "Team2"
t2.Name = NewTestId()
t2.Email = MakeEmail()
t2.Type = model.TeamOpen
_, err = ss.Team().Save(&t2)
require.NoError(t, err)
o1 := model.Channel{}
o1.TeamId = t1.Id
o1.DisplayName = "Channel1"
o1.Name = NewTestId()
o1.Type = model.ChannelTypeOpen
_, nErr := ss.Channel().Save(&o1, -1)
require.NoError(t, nErr)
o2 := model.Channel{}
o2.TeamId = o1.TeamId
o2.DisplayName = "Channel2"
o2.Name = NewTestId()
o2.Type = model.ChannelTypeOpen
_, nErr = ss.Channel().Save(&o2, -1)
require.NoError(t, nErr)
o3 := model.Channel{}
o3.TeamId = t2.Id
o3.DisplayName = "Channel3"
o3.Name = NewTestId()
o3.Type = model.ChannelTypeOpen
_, nErr = ss.Channel().Save(&o3, -1)
require.NoError(t, nErr)
m1 := model.ChannelMember{}
m1.ChannelId = o1.Id
m1.UserId = model.NewId()
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
_, err = ss.Channel().SaveMember(&m1)
require.NoError(t, err)
m2 := model.ChannelMember{}
m2.ChannelId = o2.Id
m2.UserId = m1.UserId
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
_, err = ss.Channel().SaveMember(&m2)
require.NoError(t, err)
m3 := model.ChannelMember{}
m3.ChannelId = o3.Id
m3.UserId = m1.UserId
m3.NotifyProps = model.GetDefaultChannelNotifyProps()
_, err = ss.Channel().SaveMember(&m3)
require.NoError(t, err)
t.Run("with channels", func(t *testing.T) {
var members model.ChannelMembers
opts := &store.ChannelMemberGraphQLSearchOpts{
Limit: 1,
}
members, err = ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", opts)
require.NoError(t, err)
assert.Len(t, members, 1)
opts.Limit = 3
members, err = ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", opts)
require.NoError(t, err)
assert.Len(t, members, 3)
opts.AfterChannel = members[0].ChannelId
opts.AfterUser = m1.UserId
opts.Limit = 1
members, err = ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", opts)
require.NoError(t, err)
assert.Len(t, members, 1)
})
t.Run("with channels and direct messages", func(t *testing.T) {
user := model.User{Id: m1.UserId}
u1 := model.User{Id: model.NewId()}
u2 := model.User{Id: model.NewId()}
u3 := model.User{Id: model.NewId()}
u4 := model.User{Id: model.NewId()}
_, nErr = ss.Channel().CreateDirectChannel(&u1, &user)
require.NoError(t, nErr)
_, nErr = ss.Channel().CreateDirectChannel(&u2, &user)
require.NoError(t, nErr)
// other user direct message
_, nErr = ss.Channel().CreateDirectChannel(&u3, &u4)
require.NoError(t, nErr)
opts := &store.ChannelMemberGraphQLSearchOpts{
Limit: 10,
}
members, err2 := ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", opts)
require.NoError(t, err2)
assert.Len(t, members, 5)
opts.Limit = 2
members, err2 = ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", opts)
require.NoError(t, err2)
assert.Len(t, members, 2)
opts.AfterChannel = members[1].ChannelId
opts.AfterUser = m1.UserId
opts.Limit = 2
members, err2 = ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", opts)
require.NoError(t, err2)
assert.Len(t, members, 2)
})
t.Run("for a specific team", func(t *testing.T) {
opts := &store.ChannelMemberGraphQLSearchOpts{
Limit: 10,
}
members, err2 := ss.Channel().GetMembersForUserWithCursor(m1.UserId, t2.Id, opts)
require.NoError(t, err2)
assert.Len(t, members, 3)
})
t.Run("excluding a team", func(t *testing.T) {
opts := &store.ChannelMemberGraphQLSearchOpts{
Limit: 10,
ExcludeTeam: true,
}
members, err2 := ss.Channel().GetMembersForUserWithCursor(m1.UserId, t2.Id, opts)
require.NoError(t, err2)
assert.Len(t, members, 2)
})
t.Run("with channels, direct channels and group messages", func(t *testing.T) {
userIds := []string{model.NewId(), model.NewId(), model.NewId(), m1.UserId}
group := &model.Channel{
Name: model.GetGroupNameFromUserIds(userIds),
DisplayName: "test",
Type: model.ChannelTypeGroup,
}
var channel *model.Channel
channel, nErr = ss.Channel().Save(group, 10000)
require.NoError(t, nErr)
for _, userId := range userIds {
cm := &model.ChannelMember{
UserId: userId,
ChannelId: channel.Id,
NotifyProps: model.GetDefaultChannelNotifyProps(),
SchemeUser: true,
}
_, err = ss.Channel().SaveMember(cm)
require.NoError(t, err)
}
opts := &store.ChannelMemberGraphQLSearchOpts{
Limit: 10,
}
members, err := ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", opts)
require.NoError(t, err)
assert.Len(t, members, 6)
opts.Limit = 2
members, err = ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", opts)
require.NoError(t, err)
assert.Len(t, members, 2)
opts.AfterChannel = members[1].ChannelId
opts.AfterUser = m1.UserId
opts.Limit = 10
members, err = ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", opts)
require.NoError(t, err)
assert.Len(t, members, 4)
})
}
func testChannelStoreGetMembersForUserWithPagination(t *testing.T, ss store.Store) { func testChannelStoreGetMembersForUserWithPagination(t *testing.T, ss store.Store) {
t1 := model.Team{ t1 := model.Team{
DisplayName: "team1", DisplayName: "team1",

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

@@ -435,50 +435,6 @@ func testCreateInitialSidebarCategories(t *testing.T, ss store.Store) {
require.NoError(t, nErr) require.NoError(t, nErr)
require.Equal(t, categories, categories2) require.Equal(t, categories, categories2)
}) })
t.Run("graphQL path to create initial favorites/channels/DMs categories on different teams", func(t *testing.T) {
userId := model.NewId()
t1 := &model.Team{
DisplayName: "DisplayName",
Name: NewTestId(),
Email: MakeEmail(),
Type: model.TeamOpen,
InviteId: model.NewId(),
}
t1, err := ss.Team().Save(t1)
require.NoError(t, err)
m1 := &model.TeamMember{TeamId: t1.Id, UserId: userId}
_, nErr := ss.Team().SaveMember(m1, -1)
require.NoError(t, nErr)
t2 := &model.Team{
DisplayName: "DisplayName2",
Name: NewTestId(),
Email: MakeEmail(),
Type: model.TeamOpen,
InviteId: model.NewId(),
}
t2, err = ss.Team().Save(t2)
require.NoError(t, err)
m2 := &model.TeamMember{TeamId: t2.Id, UserId: userId}
_, nErr = ss.Team().SaveMember(m2, -1)
require.NoError(t, nErr)
opts := &store.SidebarCategorySearchOpts{
TeamID: t1.Id,
ExcludeTeam: true,
}
res, nErr := ss.Channel().CreateInitialSidebarCategories(c, userId, opts)
require.NoError(t, nErr)
require.NotEmpty(t, res)
for _, cat := range res.Categories {
assert.Equal(t, t2.Id, cat.TeamId)
}
})
} }
func testCreateSidebarCategory(t *testing.T, ss store.Store) { func testCreateSidebarCategory(t *testing.T, ss store.Store) {

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

@@ -976,32 +976,6 @@ func (_m *ChannelStore) GetChannelsMemberCount(channelIDs []string) (map[string]
return r0, r1 return r0, r1
} }
// GetChannelsWithCursor provides a mock function with given fields: teamId, userId, opts, afterChannelID
func (_m *ChannelStore) GetChannelsWithCursor(teamId string, userId string, opts *model.ChannelSearchOpts, afterChannelID string) (model.ChannelList, error) {
ret := _m.Called(teamId, userId, opts, afterChannelID)
var r0 model.ChannelList
var r1 error
if rf, ok := ret.Get(0).(func(string, string, *model.ChannelSearchOpts, string) (model.ChannelList, error)); ok {
return rf(teamId, userId, opts, afterChannelID)
}
if rf, ok := ret.Get(0).(func(string, string, *model.ChannelSearchOpts, string) model.ChannelList); ok {
r0 = rf(teamId, userId, opts, afterChannelID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(model.ChannelList)
}
}
if rf, ok := ret.Get(1).(func(string, string, *model.ChannelSearchOpts, string) error); ok {
r1 = rf(teamId, userId, opts, afterChannelID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetChannelsWithTeamDataByIds provides a mock function with given fields: channelIds, includeDeleted // GetChannelsWithTeamDataByIds provides a mock function with given fields: channelIds, includeDeleted
func (_m *ChannelStore) GetChannelsWithTeamDataByIds(channelIds []string, includeDeleted bool) ([]*model.ChannelWithTeamData, error) { func (_m *ChannelStore) GetChannelsWithTeamDataByIds(channelIds []string, includeDeleted bool) ([]*model.ChannelWithTeamData, error) {
ret := _m.Called(channelIds, includeDeleted) ret := _m.Called(channelIds, includeDeleted)
@@ -1444,32 +1418,6 @@ func (_m *ChannelStore) GetMembersForUser(teamID string, userID string) (model.C
return r0, r1 return r0, r1
} }
// GetMembersForUserWithCursor provides a mock function with given fields: userID, teamID, opts
func (_m *ChannelStore) GetMembersForUserWithCursor(userID string, teamID string, opts *store.ChannelMemberGraphQLSearchOpts) (model.ChannelMembers, error) {
ret := _m.Called(userID, teamID, opts)
var r0 model.ChannelMembers
var r1 error
if rf, ok := ret.Get(0).(func(string, string, *store.ChannelMemberGraphQLSearchOpts) (model.ChannelMembers, error)); ok {
return rf(userID, teamID, opts)
}
if rf, ok := ret.Get(0).(func(string, string, *store.ChannelMemberGraphQLSearchOpts) model.ChannelMembers); ok {
r0 = rf(userID, teamID, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(model.ChannelMembers)
}
}
if rf, ok := ret.Get(1).(func(string, string, *store.ChannelMemberGraphQLSearchOpts) error); ok {
r1 = rf(userID, teamID, opts)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetMembersForUserWithPagination provides a mock function with given fields: userID, page, perPage // GetMembersForUserWithPagination provides a mock function with given fields: userID, page, perPage
func (_m *ChannelStore) GetMembersForUserWithPagination(userID string, page int, perPage int) (model.ChannelMembersWithTeamData, error) { func (_m *ChannelStore) GetMembersForUserWithPagination(userID string, page int, perPage int) (model.ChannelMembersWithTeamData, error) {
ret := _m.Called(userID, page, perPage) ret := _m.Called(userID, page, perPage)

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

@@ -1243,22 +1243,6 @@ func (s *TimerLayerChannelStore) GetChannelsMemberCount(channelIDs []string) (ma
return result, err return result, err
} }
func (s *TimerLayerChannelStore) GetChannelsWithCursor(teamId string, userId string, opts *model.ChannelSearchOpts, afterChannelID string) (model.ChannelList, error) {
start := time.Now()
result, err := s.ChannelStore.GetChannelsWithCursor(teamId, userId, opts, afterChannelID)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("ChannelStore.GetChannelsWithCursor", success, elapsed)
}
return result, err
}
func (s *TimerLayerChannelStore) GetChannelsWithTeamDataByIds(channelIds []string, includeDeleted bool) ([]*model.ChannelWithTeamData, error) { func (s *TimerLayerChannelStore) GetChannelsWithTeamDataByIds(channelIds []string, includeDeleted bool) ([]*model.ChannelWithTeamData, error) {
start := time.Now() start := time.Now()
@@ -1531,22 +1515,6 @@ func (s *TimerLayerChannelStore) GetMembersForUser(teamID string, userID string)
return result, err return result, err
} }
func (s *TimerLayerChannelStore) GetMembersForUserWithCursor(userID string, teamID string, opts *store.ChannelMemberGraphQLSearchOpts) (model.ChannelMembers, error) {
start := time.Now()
result, err := s.ChannelStore.GetMembersForUserWithCursor(userID, teamID, opts)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("ChannelStore.GetMembersForUserWithCursor", success, elapsed)
}
return result, err
}
func (s *TimerLayerChannelStore) GetMembersForUserWithPagination(userID string, page int, perPage int) (model.ChannelMembersWithTeamData, error) { func (s *TimerLayerChannelStore) GetMembersForUserWithPagination(userID string, page int, perPage int) (model.ChannelMembersWithTeamData, error) {
start := time.Now() start := time.Now()

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

@@ -19,15 +19,12 @@ import (
) )
type Context struct { type Context struct {
App app.AppIface App app.AppIface
AppContext *request.Context AppContext *request.Context
Logger *mlog.Logger Logger *mlog.Logger
Params *Params Params *Params
Err *model.AppError Err *model.AppError
// This is used to track the graphQL query that's being executed, siteURLHeader string
// so that we can monitor the timings in Grafana.
GraphQLOperationName string
siteURLHeader string
} }
// LogAuditRec logs an audit record using default LevelAPI. // LogAuditRec logs an audit record using default LevelAPI.

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

@@ -408,14 +408,7 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != model.APIURLSuffix+"/websocket" { if r.URL.Path != model.APIURLSuffix+"/websocket" {
elapsed := float64(time.Since(now)) / float64(time.Second) elapsed := float64(time.Since(now)) / float64(time.Second)
var endpoint string c.App.Metrics().ObserveAPIEndpointDuration(h.HandlerName, r.Method, statusCode, elapsed)
if strings.HasPrefix(r.URL.Path, model.APIURLSuffixV5) {
// It's a graphQL query, so use the operation name.
endpoint = c.GraphQLOperationName
} else {
endpoint = h.HandlerName
}
c.App.Metrics().ObserveAPIEndpointDuration(endpoint, r.Method, statusCode, elapsed)
} }
} }
} }

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

@@ -25,8 +25,6 @@ require (
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/gorilla/schema v1.2.0 github.com/gorilla/schema v1.2.0
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/graph-gophers/dataloader/v6 v6.0.0
github.com/graph-gophers/graphql-go v1.5.1-0.20230110080634-edea822f558a
github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c
github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-multierror v1.1.1
github.com/icrowley/fake v0.0.0-20221112152111-d7b7e2276db2 github.com/icrowley/fake v0.0.0-20221112152111-d7b7e2276db2
@@ -121,6 +119,7 @@ require (
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/gorilla/css v1.0.0 // indirect github.com/gorilla/css v1.0.0 // indirect
github.com/graph-gophers/graphql-go v1.5.1-0.20230110080634-edea822f558a // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/go-plugin v1.4.10 // indirect github.com/hashicorp/go-plugin v1.4.10 // indirect

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

@@ -345,8 +345,6 @@ github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/graph-gophers/dataloader/v6 v6.0.0 h1:qBpmq3B8PIQesoh0EJXKGfw+ulMUb+KFl4IZOe9ScWg=
github.com/graph-gophers/dataloader/v6 v6.0.0/go.mod h1:J15OZSnOoZgMkijpbZcwCmglIDYqlUiTEE1xLPbyqZM=
github.com/graph-gophers/graphql-go v1.5.1-0.20230110080634-edea822f558a h1:i0+Se9S+2zL5CBxJouqn2Ej6UQMwH1c57ZB6DVnqck4= github.com/graph-gophers/graphql-go v1.5.1-0.20230110080634-edea822f558a h1:i0+Se9S+2zL5CBxJouqn2Ej6UQMwH1c57ZB6DVnqck4=
github.com/graph-gophers/graphql-go v1.5.1-0.20230110080634-edea822f558a/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os= github.com/graph-gophers/graphql-go v1.5.1-0.20230110080634-edea822f558a/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
@@ -520,7 +518,6 @@ github.com/otiai10/gosseract/v2 v2.4.0/go.mod h1:fhbIDRh29bj13vni6RT3gtWKjKCAeqD
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.3 h1:7JgpsBaN0uMkyju4tbYHu0mnM55hNKVYLsXmwr15NQI= github.com/otiai10/mint v1.3.3 h1:7JgpsBaN0uMkyju4tbYHu0mnM55hNKVYLsXmwr15NQI=
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=

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

@@ -10,7 +10,6 @@ require (
github.com/golang/mock v1.6.0 github.com/golang/mock v1.6.0
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/graph-gophers/graphql-go v1.5.1-0.20230110080634-edea822f558a
github.com/hashicorp/go-hclog v1.5.0 github.com/hashicorp/go-hclog v1.5.0
github.com/hashicorp/go-plugin v1.4.10 github.com/hashicorp/go-plugin v1.4.10
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9

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

@@ -40,9 +40,6 @@ github.com/go-asn1-ber/asn1-ber v1.3.2-0.20191121212151-29be175fc3a3/go.mod h1:h
github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -60,7 +57,6 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
@@ -76,8 +72,6 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/graph-gophers/graphql-go v1.5.1-0.20230110080634-edea822f558a h1:i0+Se9S+2zL5CBxJouqn2Ej6UQMwH1c57ZB6DVnqck4=
github.com/graph-gophers/graphql-go v1.5.1-0.20230110080634-edea822f558a/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
@@ -131,7 +125,6 @@ github.com/nicksnyder/go-i18n/v2 v2.0.3 h1:ks/JkQiOEhhuF6jpNvx+Wih1NIiXzUnZeZVnJ
github.com/nicksnyder/go-i18n/v2 v2.0.3/go.mod h1:oDab7q8XCYMRlcrBnaY/7B1eOectbvj6B1UPBT+p5jo= github.com/nicksnyder/go-i18n/v2 v2.0.3/go.mod h1:oDab7q8XCYMRlcrBnaY/7B1eOectbvj6B1UPBT+p5jo=
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
@@ -190,7 +183,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -222,8 +214,6 @@ github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63M
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI=
go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=

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

@@ -7,7 +7,6 @@ import (
"crypto/sha1" "crypto/sha1"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"io" "io"
"net/http" "net/http"
"regexp" "regexp"
@@ -201,47 +200,6 @@ func WithID(ID string) ChannelOption {
} }
} }
// The following are some GraphQL methods necessary to return the
// data in float64 type. The spec doesn't support 64 bit integers,
// so we have to pass the data in float64. The _ at the end is
// a hack to keep the attribute name same in GraphQL schema.
func (o *Channel) CreateAt_() float64 {
return float64(o.CreateAt)
}
func (o *Channel) UpdateAt_() float64 {
return float64(o.UpdateAt)
}
func (o *Channel) DeleteAt_() float64 {
return float64(o.DeleteAt)
}
func (o *Channel) LastPostAt_() float64 {
return float64(o.LastPostAt)
}
func (o *Channel) TotalMsgCount_() float64 {
return float64(o.TotalMsgCount)
}
func (o *Channel) TotalMsgCountRoot_() float64 {
return float64(o.TotalMsgCountRoot)
}
func (o *Channel) LastRootPostAt_() float64 {
return float64(o.LastRootPostAt)
}
func (o *Channel) ExtraUpdateAt_() float64 {
return float64(o.ExtraUpdateAt)
}
func (o *Channel) Props_() StringInterface {
return StringInterface(o.Props)
}
func (o *Channel) DeepCopy() *Channel { func (o *Channel) DeepCopy() *Channel {
cCopy := *o cCopy := *o
if cCopy.SchemeId != nil { if cCopy.SchemeId != nil {
@@ -391,24 +349,10 @@ func (o *Channel) GetOtherUserIdForDM(userId string) string {
return otherUserId return otherUserId
} }
func (ChannelType) ImplementsGraphQLType(name string) bool {
return name == "ChannelType"
}
func (t ChannelType) MarshalJSON() ([]byte, error) { func (t ChannelType) MarshalJSON() ([]byte, error) {
return json.Marshal(string(t)) return json.Marshal(string(t))
} }
func (t *ChannelType) UnmarshalGraphQL(input any) error {
chType, ok := input.(string)
if !ok {
return errors.New("wrong type")
}
*t = ChannelType(chType)
return nil
}
func GetDMNameFromIds(userId1, userId2 string) string { func GetDMNameFromIds(userId1, userId2 string) string {
if userId1 > userId2 { if userId1 > userId2 {
return userId2 + "__" + userId1 return userId2 + "__" + userId1

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

@@ -88,39 +88,6 @@ func (o *ChannelMember) Auditable() map[string]interface{} {
} }
} }
// The following are some GraphQL methods necessary to return the
// data in float64 type. The spec doesn't support 64 bit integers,
// so we have to pass the data in float64. The _ at the end is
// a hack to keep the attribute name same in GraphQL schema.
func (o *ChannelMember) LastViewedAt_() float64 {
return float64(o.LastViewedAt)
}
func (o *ChannelMember) MsgCount_() float64 {
return float64(o.MsgCount)
}
func (o *ChannelMember) MentionCount_() float64 {
return float64(o.MentionCount)
}
func (o *ChannelMember) MentionCountRoot_() float64 {
return float64(o.MentionCountRoot)
}
func (o *ChannelMember) UrgentMentionCount_() float64 {
return float64(o.UrgentMentionCount)
}
func (o *ChannelMember) MsgCountRoot_() float64 {
return float64(o.MsgCountRoot)
}
func (o *ChannelMember) LastUpdateAt_() float64 {
return float64(o.LastUpdateAt)
}
// ChannelMemberWithTeamData contains ChannelMember appended with extra team information // ChannelMemberWithTeamData contains ChannelMember appended with extra team information
// as well. // as well.
type ChannelMemberWithTeamData struct { type ChannelMemberWithTeamData struct {

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

@@ -5,7 +5,6 @@ package model
import ( import (
"encoding/json" "encoding/json"
"errors"
"regexp" "regexp"
) )
@@ -89,42 +88,10 @@ func IsValidCategoryId(s string) bool {
return categoryIdPattern.MatchString(s) return categoryIdPattern.MatchString(s)
} }
func (SidebarCategoryType) ImplementsGraphQLType(name string) bool {
return name == "SidebarCategoryType"
}
func (t SidebarCategoryType) MarshalJSON() ([]byte, error) { func (t SidebarCategoryType) MarshalJSON() ([]byte, error) {
return json.Marshal(string(t)) return json.Marshal(string(t))
} }
func (t *SidebarCategoryType) UnmarshalGraphQL(input any) error {
chType, ok := input.(string)
if !ok {
return errors.New("wrong type")
}
*t = SidebarCategoryType(chType)
return nil
}
func (SidebarCategorySorting) ImplementsGraphQLType(name string) bool {
return name == "SidebarCategorySorting"
}
func (t SidebarCategorySorting) MarshalJSON() ([]byte, error) { func (t SidebarCategorySorting) MarshalJSON() ([]byte, error) {
return json.Marshal(string(t)) return json.Marshal(string(t))
} }
func (t *SidebarCategorySorting) UnmarshalGraphQL(input any) error {
chType, ok := input.(string)
if !ok {
return errors.New("wrong type")
}
*t = SidebarCategorySorting(chType)
return nil
}
func (t *SidebarCategory) SortOrder_() float64 {
return float64(t.SortOrder)
}

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

@@ -8,8 +8,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"time" "time"
"github.com/graph-gophers/graphql-go"
) )
const ( const (
@@ -63,12 +61,6 @@ func (cs *CustomStatus) AreDurationAndExpirationTimeValid() bool {
return false return false
} }
// ExpiresAt_ returns the time in a type that has the marshal/unmarshal methods
// attached to it.
func (cs *CustomStatus) ExpiresAt_() graphql.Time {
return graphql.Time{Time: cs.ExpiresAt}
}
func RuneToHexadecimalString(r rune) string { func RuneToHexadecimalString(r rune) string {
return fmt.Sprintf("%04x", r) return fmt.Sprintf("%04x", r)
} }

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

@@ -29,9 +29,6 @@ type FeatureFlags struct {
NormalizeLdapDNs bool NormalizeLdapDNs bool
// Enable GraphQL feature
GraphQL bool
PostPriority bool PostPriority bool
// Enable WYSIWYG text editor // Enable WYSIWYG text editor
@@ -54,7 +51,6 @@ func (f *FeatureFlags) SetDefaults() {
f.EnableRemoteClusterService = false f.EnableRemoteClusterService = false
f.AppsEnabled = true f.AppsEnabled = true
f.NormalizeLdapDNs = false f.NormalizeLdapDNs = false
f.GraphQL = false
f.CallsEnabled = true f.CallsEnabled = true
f.DeprecateCloudFree = false f.DeprecateCloudFree = false
f.WysiwygEditor = false f.WysiwygEditor = false

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

@@ -34,19 +34,6 @@ func (s *Status) ToJSON() ([]byte, error) {
return json.Marshal(sCopy) return json.Marshal(sCopy)
} }
// The following are some GraphQL methods necessary to return the
// data in float64 type. The spec doesn't support 64 bit integers,
// so we have to pass the data in float64. The _ at the end is
// a hack to keep the attribute name same in GraphQL schema.
func (s *Status) LastActivityAt_() float64 {
return float64(s.LastActivityAt)
}
func (s *Status) DNDEndTime_() float64 {
return float64(s.DNDEndTime)
}
func StatusListToJSON(u []*Status) ([]byte, error) { func StatusListToJSON(u []*Status) ([]byte, error) {
list := make([]Status, len(u)) list := make([]Status, len(u))
for i, s := range u { for i, s := range u {

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

@@ -287,24 +287,3 @@ func (o *Team) ShallowCopy() *Team {
c := *o c := *o
return &c return &c
} }
// The following are some GraphQL methods necessary to return the
// data in float64 type. The spec doesn't support 64 bit integers,
// so we have to pass the data in float64. The _ at the end is
// a hack to keep the attribute name same in GraphQL schema.
func (o *Team) CreateAt_() float64 {
return float64(o.UpdateAt)
}
func (o *Team) UpdateAt_() float64 {
return float64(o.UpdateAt)
}
func (o *Team) DeleteAt_() float64 {
return float64(o.DeleteAt)
}
func (o *Team) LastTeamIconUpdate_() float64 {
return float64(o.LastTeamIconUpdate)
}

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

@@ -142,9 +142,3 @@ func (o *TeamMember) PreUpdate() {
func (o *TeamMember) GetRoles() []string { func (o *TeamMember) GetRoles() []string {
return strings.Fields(o.Roles) return strings.Fields(o.Roles)
} }
// DeleteAt_ returns the deleteAt value in float64. This is necessary to work
// with GraphQL since it doesn't support 64 bit integers.
func (o *TeamMember) DeleteAt_() float64 {
return float64(o.DeleteAt)
}

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

@@ -468,47 +468,6 @@ func (u *User) PreSave() {
} }
} }
// The following are some GraphQL methods necessary to return the
// data in float64 type. The spec doesn't support 64 bit integers,
// so we have to pass the data in float64. The _ at the end is
// a hack to keep the attribute name same in GraphQL schema.
func (u *User) CreateAt_() float64 {
return float64(u.CreateAt)
}
func (u *User) DeleteAt_() float64 {
return float64(u.DeleteAt)
}
func (u *User) UpdateAt_() float64 {
return float64(u.UpdateAt)
}
func (u *User) LastPictureUpdate_() float64 {
return float64(u.LastPictureUpdate)
}
func (u *User) LastPasswordUpdate_() float64 {
return float64(u.LastPasswordUpdate)
}
func (u *User) FailedAttempts_() float64 {
return float64(u.FailedAttempts)
}
func (u *User) LastActivityAt_() float64 {
return float64(u.LastActivityAt)
}
func (u *User) BotLastIconUpdate_() float64 {
return float64(u.BotLastIconUpdate)
}
func (u *User) TermsOfServiceCreateAt_() float64 {
return float64(u.TermsOfServiceCreateAt)
}
// PreUpdate should be run before updating the user in the db. // PreUpdate should be run before updating the user in the db.
func (u *User) PreUpdate() { func (u *User) PreUpdate() {
u.Username = SanitizeUnicode(u.Username) u.Username = SanitizeUnicode(u.Username)

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

@@ -177,24 +177,10 @@ func (m StringMap) Value() (driver.Value, error) {
return string(buf), nil return string(buf), nil
} }
func (StringMap) ImplementsGraphQLType(name string) bool {
return name == "StringMap"
}
func (m StringMap) MarshalJSON() ([]byte, error) { func (m StringMap) MarshalJSON() ([]byte, error) {
return json.Marshal((map[string]string)(m)) return json.Marshal((map[string]string)(m))
} }
func (m *StringMap) UnmarshalGraphQL(input any) error {
json, ok := input.(map[string]string)
if !ok {
return errors.New("wrong type")
}
*m = json
return nil
}
func (si *StringInterface) Scan(value any) error { func (si *StringInterface) Scan(value any) error {
if value == nil { if value == nil {
return nil return nil
@@ -228,24 +214,10 @@ func (si StringInterface) Value() (driver.Value, error) {
return string(j), err return string(j), err
} }
func (StringInterface) ImplementsGraphQLType(name string) bool {
return name == "StringInterface"
}
func (si StringInterface) MarshalJSON() ([]byte, error) { func (si StringInterface) MarshalJSON() ([]byte, error) {
return json.Marshal((map[string]any)(si)) return json.Marshal((map[string]any)(si))
} }
func (si *StringInterface) UnmarshalGraphQL(input any) error {
json, ok := input.(map[string]any)
if !ok {
return errors.New("wrong type")
}
*si = json
return nil
}
var translateFunc i18n.TranslateFunc var translateFunc i18n.TranslateFunc
var translateFuncOnce sync.Once var translateFuncOnce sync.Once

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

@@ -5,9 +5,10 @@
package mock_oauther package mock_oauther
import ( import (
reflect "reflect"
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
oauth2 "golang.org/x/oauth2" oauth2 "golang.org/x/oauth2"
reflect "reflect"
) )
// MockOAuther is a mock of OAuther interface // MockOAuther is a mock of OAuther interface

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

@@ -1,23 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package mlog
import (
"context"
)
// GraphQLLogger is used to log panics that occur during query execution.
type GraphQLLogger struct {
logger *Logger
}
func NewGraphQLLogger(logger *Logger) *GraphQLLogger {
return &GraphQLLogger{logger: logger}
}
// LogPanic satisfies the graphql/log.Logger interface.
// It converts the panic into an error.
func (l *GraphQLLogger) LogPanic(_ context.Context, value any) {
l.logger.Error("Error while executing GraphQL query", Any("error", value))
}