From 3dd9e3715ca7f61c93d9fe6c22425c62889f09e2 Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Thu, 12 Oct 2023 09:47:35 +0530 Subject: [PATCH] Goodbye, GraphQL (#24827) ```release-note NONE ``` --- server/Makefile | 4 - server/channels/api4/api.go | 5 - server/channels/api4/apitestlib.go | 37 -- server/channels/api4/graphql.go | 193 ------- server/channels/api4/graphql_client.go | 87 --- server/channels/api4/graphql_test.go | 34 -- server/channels/api4/resolver.go | 427 --------------- server/channels/api4/resolver_channel.go | 157 ------ .../channels/api4/resolver_channel_member.go | 226 -------- .../api4/resolver_channel_member_test.go | 400 -------------- server/channels/api4/resolver_channel_test.go | 513 ------------------ .../api4/resolver_sidebar_categories_test.go | 142 ----- server/channels/api4/resolver_team.go | 95 ---- server/channels/api4/resolver_team_member.go | 50 -- .../api4/resolver_team_member_test.go | 413 -------------- server/channels/api4/resolver_test.go | 229 -------- server/channels/api4/resolver_user.go | 222 -------- server/channels/api4/resolver_user_test.go | 244 --------- server/channels/api4/schema.graphqls | 222 -------- server/channels/app/app_iface.go | 1 - server/channels/app/channel.go | 8 - .../app/opentracing/opentracing_layer.go | 22 - .../channels/store/layer_generators/main.go | 4 +- .../opentracinglayer/opentracinglayer.go | 36 -- .../channels/store/retrylayer/retrylayer.go | 42 -- .../channels/store/sqlstore/channel_store.go | 140 ----- server/channels/store/store.go | 12 - .../channels/store/storetest/channel_store.go | 336 ------------ .../storetest/channel_store_categories.go | 44 -- .../store/storetest/mocks/ChannelStore.go | 52 -- .../channels/store/timerlayer/timerlayer.go | 32 -- server/channels/web/context.go | 15 +- server/channels/web/handlers.go | 9 +- server/go.mod | 3 +- server/go.sum | 3 - server/public/go.mod | 1 - server/public/go.sum | 10 - server/public/model/channel.go | 56 -- server/public/model/channel_member.go | 33 -- server/public/model/channel_sidebar.go | 33 -- server/public/model/custom_status.go | 8 - server/public/model/feature_flags.go | 4 - server/public/model/status.go | 13 - server/public/model/team.go | 21 - server/public/model/team_member.go | 6 - server/public/model/user.go | 41 -- server/public/model/utils.go | 28 - .../oauther/mock_oauther/mock_oauther.go | 3 +- server/public/shared/mlog/graphql.go | 23 - 49 files changed, 12 insertions(+), 4727 deletions(-) delete mode 100644 server/channels/api4/graphql.go delete mode 100644 server/channels/api4/graphql_client.go delete mode 100644 server/channels/api4/graphql_test.go delete mode 100644 server/channels/api4/resolver.go delete mode 100644 server/channels/api4/resolver_channel.go delete mode 100644 server/channels/api4/resolver_channel_member.go delete mode 100644 server/channels/api4/resolver_channel_member_test.go delete mode 100644 server/channels/api4/resolver_channel_test.go delete mode 100644 server/channels/api4/resolver_sidebar_categories_test.go delete mode 100644 server/channels/api4/resolver_team.go delete mode 100644 server/channels/api4/resolver_team_member.go delete mode 100644 server/channels/api4/resolver_team_member_test.go delete mode 100644 server/channels/api4/resolver_test.go delete mode 100644 server/channels/api4/resolver_user.go delete mode 100644 server/channels/api4/resolver_user_test.go delete mode 100644 server/channels/api4/schema.graphqls delete mode 100644 server/public/shared/mlog/graphql.go diff --git a/server/Makefile b/server/Makefile index bb2c437a12..1023eb21bb 100644 --- a/server/Makefile +++ b/server/Makefile @@ -50,10 +50,6 @@ ifeq ($(BUILD_NUMBER),) BUILD_NUMBER := dev endif -ifeq ($(BUILD_NUMBER),dev) - export MM_FEATUREFLAGS_GRAPHQL = true -endif - # Ensure developer invocation and tests are anchored. MM_SERVER_PATH ?= $(ROOT) diff --git a/server/channels/api4/api.go b/server/channels/api4/api.go index d20fdd50fb..5275df3b4b 100644 --- a/server/channels/api4/api.go +++ b/server/channels/api4/api.go @@ -7,7 +7,6 @@ import ( "net/http" "github.com/gorilla/mux" - graphql "github.com/graph-gophers/graphql-go" _ "github.com/mattermost/go-i18n/i18n" "github.com/mattermost/mattermost/server/public/model" @@ -142,7 +141,6 @@ type Routes struct { type API struct { srv *app.Server - schema *graphql.Schema BaseRoutes *Routes } @@ -306,9 +304,6 @@ func Init(srv *app.Server) (*API, error) { api.InitUsage() api.InitHostedCustomer() api.InitDrafts() - if err := api.InitGraphQL(); err != nil { - return nil, err - } srv.Router.Handle("/api/v4/{anything:.*}", http.HandlerFunc(api.Handle404)) diff --git a/server/channels/api4/apitestlib.go b/server/channels/api4/apitestlib.go index 449d4c6208..4e56583c84 100644 --- a/server/channels/api4/apitestlib.go +++ b/server/channels/api4/apitestlib.go @@ -4,9 +4,7 @@ package api4 import ( - "bytes" "context" - "encoding/json" "errors" "fmt" "io" @@ -21,7 +19,6 @@ import ( "time" "github.com/gorilla/websocket" - graphql "github.com/graph-gophers/graphql-go" s3 "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" "github.com/stretchr/testify/require" @@ -47,7 +44,6 @@ type TestHelper struct { Context *request.Context Client *model.Client4 - GraphQLClient *graphQLClient BasicUser *model.User BasicUser2 *model.User TeamAdminUser *model.User @@ -196,7 +192,6 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent } th.Client = th.CreateClient() - th.GraphQLClient = newGraphQLClient(fmt.Sprintf("http://localhost:%v", th.App.Srv().ListenAddr.Port)) th.SystemAdminClient = th.CreateClient() th.SystemManagerClient = th.CreateClient() @@ -811,16 +806,10 @@ func (th *TestHelper) CreateDmChannel(user *model.User) *model.Channel { func (th *TestHelper) LoginBasic() { th.LoginBasicWithClient(th.Client) - if os.Getenv("MM_FEATUREFLAGS_GRAPHQL") == "true" { - th.LoginBasicWithGraphQL() - } } func (th *TestHelper) LoginBasic2() { th.LoginBasic2WithClient(th.Client) - if os.Getenv("MM_FEATUREFLAGS_GRAPHQL") == "true" { - th.LoginBasicWithGraphQL() - } } 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) { _, _, err := client.Login(context.Background(), th.BasicUser2.Email, th.BasicUser2.Password) if err != nil { @@ -1301,22 +1283,3 @@ func (th *TestHelper) SetupScheme(scope string) *model.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 -} diff --git a/server/channels/api4/graphql.go b/server/channels/api4/graphql.go deleted file mode 100644 index 6212c08cdd..0000000000 --- a/server/channels/api4/graphql.go +++ /dev/null @@ -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(¶ms); 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(` - - - - GraphiQL editor | Mattermost - - - - - - - - -
Loading...
- - - -`) - -// isProd is a helper function to apply prod-specific graphQL validations. -func isProd() bool { - return model.BuildNumber != "dev" -} diff --git a/server/channels/api4/graphql_client.go b/server/channels/api4/graphql_client.go deleted file mode 100644 index 975d350aee..0000000000 --- a/server/channels/api4/graphql_client.go +++ /dev/null @@ -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 -} diff --git a/server/channels/api4/graphql_test.go b/server/channels/api4/graphql_test.go deleted file mode 100644 index 2f24f1b4d2..0000000000 --- a/server/channels/api4/graphql_test.go +++ /dev/null @@ -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") -} diff --git a/server/channels/api4/resolver.go b/server/channels/api4/resolver.go deleted file mode 100644 index bdd1b79329..0000000000 --- a/server/channels/api4/resolver.go +++ /dev/null @@ -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 -} diff --git a/server/channels/api4/resolver_channel.go b/server/channels/api4/resolver_channel.go deleted file mode 100644 index 6dbe080930..0000000000 --- a/server/channels/api4/resolver_channel.go +++ /dev/null @@ -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 -} diff --git a/server/channels/api4/resolver_channel_member.go b/server/channels/api4/resolver_channel_member.go deleted file mode 100644 index c7e7018bd7..0000000000 --- a/server/channels/api4/resolver_channel_member.go +++ /dev/null @@ -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 -} diff --git a/server/channels/api4/resolver_channel_member_test.go b/server/channels/api4/resolver_channel_member_test.go deleted file mode 100644 index 02198c6584..0000000000 --- a/server/channels/api4/resolver_channel_member_test.go +++ /dev/null @@ -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) -} diff --git a/server/channels/api4/resolver_channel_test.go b/server/channels/api4/resolver_channel_test.go deleted file mode 100644 index e36b670c16..0000000000 --- a/server/channels/api4/resolver_channel_test.go +++ /dev/null @@ -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) -} diff --git a/server/channels/api4/resolver_sidebar_categories_test.go b/server/channels/api4/resolver_sidebar_categories_test.go deleted file mode 100644 index 65d69905f6..0000000000 --- a/server/channels/api4/resolver_sidebar_categories_test.go +++ /dev/null @@ -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) - } -} diff --git a/server/channels/api4/resolver_team.go b/server/channels/api4/resolver_team.go deleted file mode 100644 index 6fedd8174a..0000000000 --- a/server/channels/api4/resolver_team.go +++ /dev/null @@ -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 -} diff --git a/server/channels/api4/resolver_team_member.go b/server/channels/api4/resolver_team_member.go deleted file mode 100644 index 5facac57d4..0000000000 --- a/server/channels/api4/resolver_team_member.go +++ /dev/null @@ -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 -} diff --git a/server/channels/api4/resolver_team_member_test.go b/server/channels/api4/resolver_team_member_test.go deleted file mode 100644 index e371b2e1ce..0000000000 --- a/server/channels/api4/resolver_team_member_test.go +++ /dev/null @@ -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) - }) -} diff --git a/server/channels/api4/resolver_test.go b/server/channels/api4/resolver_test.go deleted file mode 100644 index 13b6111f39..0000000000 --- a/server/channels/api4/resolver_test.go +++ /dev/null @@ -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) - } -} diff --git a/server/channels/api4/resolver_user.go b/server/channels/api4/resolver_user.go deleted file mode 100644 index dcacd6f61c..0000000000 --- a/server/channels/api4/resolver_user.go +++ /dev/null @@ -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 -} diff --git a/server/channels/api4/resolver_user_test.go b/server/channels/api4/resolver_user_test.go deleted file mode 100644 index 267bf5fb35..0000000000 --- a/server/channels/api4/resolver_user_test.go +++ /dev/null @@ -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) - }) -} diff --git a/server/channels/api4/schema.graphqls b/server/channels/api4/schema.graphqls deleted file mode 100644 index e277832f04..0000000000 --- a/server/channels/api4/schema.graphqls +++ /dev/null @@ -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! -} diff --git a/server/channels/app/app_iface.go b/server/channels/app/app_iface.go index e1be1c577e..f23302b946 100644 --- a/server/channels/app/app_iface.go +++ b/server/channels/app/app_iface.go @@ -626,7 +626,6 @@ type AppIface interface { GetChannelsForScheme(scheme *model.Scheme, offset int, limit 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) - 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) 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) diff --git a/server/channels/app/channel.go b/server/channels/app/channel.go index f4af8bdfd7..bfc1985451 100644 --- a/server/channels/app/channel.go +++ b/server/channels/app/channel.go @@ -1938,14 +1938,6 @@ func (a *App) GetChannelsForTeamForUser(c request.CTX, teamID string, userID str 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) { list, err := a.Srv().Store().Channel().GetChannelsByUser(userID, includeDeleted, lastDeleteAt, pageSize, fromChannelID) if err != nil { diff --git a/server/channels/app/opentracing/opentracing_layer.go b/server/channels/app/opentracing/opentracing_layer.go index 66d2bb7090..1cef616724 100644 --- a/server/channels/app/opentracing/opentracing_layer.go +++ b/server/channels/app/opentracing/opentracing_layer.go @@ -5718,28 +5718,6 @@ func (a *OpenTracingAppLayer) GetChannelsForTeamForUser(c request.CTX, teamID st 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) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetChannelsForUser") diff --git a/server/channels/store/layer_generators/main.go b/server/channels/store/layer_generators/main.go index cfb95abdb8..dd92cc2603 100644 --- a/server/channels/store/layer_generators/main.go +++ b/server/channels/store/layer_generators/main.go @@ -294,7 +294,7 @@ func generateLayer(name, templateFile string) ([]byte, error) { switch param.Type { case "ChannelSearchOpts", "UserGetByIdsOpts", "ThreadMembershipOpts": 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, "*"))) default: 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 { case "ChannelSearchOpts", "UserGetByIdsOpts", "ThreadMembershipOpts": 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, "*"))) default: paramsWithType = append(paramsWithType, fmt.Sprintf("%s %s", param.Name, param.Type)) diff --git a/server/channels/store/opentracinglayer/opentracinglayer.go b/server/channels/store/opentracinglayer/opentracinglayer.go index 49355a569d..fd86e98e62 100644 --- a/server/channels/store/opentracinglayer/opentracinglayer.go +++ b/server/channels/store/opentracinglayer/opentracinglayer.go @@ -1333,24 +1333,6 @@ func (s *OpenTracingLayerChannelStore) GetChannelsMemberCount(channelIDs []strin 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) { origCtx := s.Root.Store.Context() 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 } -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) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetMembersForUserWithPagination") diff --git a/server/channels/store/retrylayer/retrylayer.go b/server/channels/store/retrylayer/retrylayer.go index 9f58341f55..a71aba933e 100644 --- a/server/channels/store/retrylayer/retrylayer.go +++ b/server/channels/store/retrylayer/retrylayer.go @@ -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) { 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) { tries := 0 diff --git a/server/channels/store/sqlstore/channel_store.go b/server/channels/store/sqlstore/channel_store.go index 60b2780ea7..78341b9602 100644 --- a/server/channels/store/sqlstore/channel_store.go +++ b/server/channels/store/sqlstore/channel_store.go @@ -1073,66 +1073,6 @@ func (s SqlChannelStore) GetChannels(teamId string, userId string, opts *model.C 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) { query := s.getQueryBuilder(). Select("Channels.*"). @@ -3050,86 +2990,6 @@ func (s SqlChannelStore) GetMembersForUser(teamID string, userID string) (model. 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) { dbMembers := channelMemberWithTeamWithSchemeRolesList{} offset := page * perPage diff --git a/server/channels/store/store.go b/server/channels/store/store.go index 67621de6e5..1b60015ba5 100644 --- a/server/channels/store/store.go +++ b/server/channels/store/store.go @@ -201,7 +201,6 @@ type ChannelStore interface { GetDeletedByName(team_id string, name string) (*model.Channel, error) GetDeleted(team_id string, offset int, limit int, userID string) (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) GetAllChannelMembersById(id string) ([]string, 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) GetTeamMembersForChannel(channelID string) ([]string, 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) AutocompleteInTeam(teamID, userID, term string, includeDeleted, isGuest 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 } -// 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 // the reminder message to the user. type PostReminderMetadata struct { diff --git a/server/channels/store/storetest/channel_store.go b/server/channels/store/storetest/channel_store.go index cd20674005..45d3212033 100644 --- a/server/channels/store/storetest/channel_store.go +++ b/server/channels/store/storetest/channel_store.go @@ -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("ChannelDeleteMemberStore", func(t *testing.T) { testChannelDeleteMemberStore(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("GetAllChannels", func(t *testing.T) { testChannelStoreGetAllChannels(t, ss, s) }) 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("GetChannelCounts", func(t *testing.T) { testChannelStoreGetChannelCounts(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("CountPostsAfter", func(t *testing.T) { testCountPostsAfter(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) } -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) { team := 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) { t1 := model.Team{ DisplayName: "team1", diff --git a/server/channels/store/storetest/channel_store_categories.go b/server/channels/store/storetest/channel_store_categories.go index ec529fd420..7af70208f1 100644 --- a/server/channels/store/storetest/channel_store_categories.go +++ b/server/channels/store/storetest/channel_store_categories.go @@ -435,50 +435,6 @@ func testCreateInitialSidebarCategories(t *testing.T, ss store.Store) { require.NoError(t, nErr) 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) { diff --git a/server/channels/store/storetest/mocks/ChannelStore.go b/server/channels/store/storetest/mocks/ChannelStore.go index cea58b35c2..53bb50726e 100644 --- a/server/channels/store/storetest/mocks/ChannelStore.go +++ b/server/channels/store/storetest/mocks/ChannelStore.go @@ -976,32 +976,6 @@ func (_m *ChannelStore) GetChannelsMemberCount(channelIDs []string) (map[string] 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 func (_m *ChannelStore) GetChannelsWithTeamDataByIds(channelIds []string, includeDeleted bool) ([]*model.ChannelWithTeamData, error) { ret := _m.Called(channelIds, includeDeleted) @@ -1444,32 +1418,6 @@ func (_m *ChannelStore) GetMembersForUser(teamID string, userID string) (model.C 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 func (_m *ChannelStore) GetMembersForUserWithPagination(userID string, page int, perPage int) (model.ChannelMembersWithTeamData, error) { ret := _m.Called(userID, page, perPage) diff --git a/server/channels/store/timerlayer/timerlayer.go b/server/channels/store/timerlayer/timerlayer.go index 2c2a20cdaf..ab90339dac 100644 --- a/server/channels/store/timerlayer/timerlayer.go +++ b/server/channels/store/timerlayer/timerlayer.go @@ -1243,22 +1243,6 @@ func (s *TimerLayerChannelStore) GetChannelsMemberCount(channelIDs []string) (ma 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) { start := time.Now() @@ -1531,22 +1515,6 @@ func (s *TimerLayerChannelStore) GetMembersForUser(teamID string, userID string) 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) { start := time.Now() diff --git a/server/channels/web/context.go b/server/channels/web/context.go index cdea7d5752..d1890d2598 100644 --- a/server/channels/web/context.go +++ b/server/channels/web/context.go @@ -19,15 +19,12 @@ import ( ) type Context struct { - App app.AppIface - AppContext *request.Context - Logger *mlog.Logger - Params *Params - Err *model.AppError - // This is used to track the graphQL query that's being executed, - // so that we can monitor the timings in Grafana. - GraphQLOperationName string - siteURLHeader string + App app.AppIface + AppContext *request.Context + Logger *mlog.Logger + Params *Params + Err *model.AppError + siteURLHeader string } // LogAuditRec logs an audit record using default LevelAPI. diff --git a/server/channels/web/handlers.go b/server/channels/web/handlers.go index 05b0e87982..0adb2c6aac 100644 --- a/server/channels/web/handlers.go +++ b/server/channels/web/handlers.go @@ -408,14 +408,7 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.URL.Path != model.APIURLSuffix+"/websocket" { elapsed := float64(time.Since(now)) / float64(time.Second) - var endpoint string - 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) + c.App.Metrics().ObserveAPIEndpointDuration(h.HandlerName, r.Method, statusCode, elapsed) } } } diff --git a/server/go.mod b/server/go.mod index 5b23edda01..cec181adfc 100644 --- a/server/go.mod +++ b/server/go.mod @@ -25,8 +25,6 @@ require ( github.com/gorilla/mux v1.8.0 github.com/gorilla/schema v1.2.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/hashicorp/go-multierror v1.1.1 github.com/icrowley/fake v0.0.0-20221112152111-d7b7e2276db2 @@ -121,6 +119,7 @@ require ( github.com/google/uuid v1.3.0 // indirect github.com/gopherjs/gopherjs v1.17.2 // 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/go-hclog v1.5.0 // indirect github.com/hashicorp/go-plugin v1.4.10 // indirect diff --git a/server/go.sum b/server/go.sum index 908ad8ac08..65a761dc0f 100644 --- a/server/go.sum +++ b/server/go.sum @@ -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/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 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/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os= 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.3 h1:7JgpsBaN0uMkyju4tbYHu0mnM55hNKVYLsXmwr15NQI= 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/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= diff --git a/server/public/go.mod b/server/public/go.mod index 3c9e32c478..f046e3744a 100644 --- a/server/public/go.mod +++ b/server/public/go.mod @@ -10,7 +10,6 @@ require ( github.com/golang/mock v1.6.0 github.com/gorilla/mux v1.8.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-plugin v1.4.10 github.com/lib/pq v1.10.9 diff --git a/server/public/go.sum b/server/public/go.sum index 5c6104dd0f..b079121912 100644 --- a/server/public/go.sum +++ b/server/public/go.sum @@ -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/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= 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/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 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/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.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 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-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/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 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/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= @@ -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/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= 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/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= 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/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 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.7.0/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.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 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= 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= diff --git a/server/public/model/channel.go b/server/public/model/channel.go index 169d87760b..eaf3c79018 100644 --- a/server/public/model/channel.go +++ b/server/public/model/channel.go @@ -7,7 +7,6 @@ import ( "crypto/sha1" "encoding/hex" "encoding/json" - "errors" "io" "net/http" "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 { cCopy := *o if cCopy.SchemeId != nil { @@ -391,24 +349,10 @@ func (o *Channel) GetOtherUserIdForDM(userId string) string { return otherUserId } -func (ChannelType) ImplementsGraphQLType(name string) bool { - return name == "ChannelType" -} - func (t ChannelType) MarshalJSON() ([]byte, error) { 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 { if userId1 > userId2 { return userId2 + "__" + userId1 diff --git a/server/public/model/channel_member.go b/server/public/model/channel_member.go index 8201fda346..584534447b 100644 --- a/server/public/model/channel_member.go +++ b/server/public/model/channel_member.go @@ -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 // as well. type ChannelMemberWithTeamData struct { diff --git a/server/public/model/channel_sidebar.go b/server/public/model/channel_sidebar.go index e8b5a6a215..8547fc3262 100644 --- a/server/public/model/channel_sidebar.go +++ b/server/public/model/channel_sidebar.go @@ -5,7 +5,6 @@ package model import ( "encoding/json" - "errors" "regexp" ) @@ -89,42 +88,10 @@ func IsValidCategoryId(s string) bool { return categoryIdPattern.MatchString(s) } -func (SidebarCategoryType) ImplementsGraphQLType(name string) bool { - return name == "SidebarCategoryType" -} - func (t SidebarCategoryType) MarshalJSON() ([]byte, error) { 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) { 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) -} diff --git a/server/public/model/custom_status.go b/server/public/model/custom_status.go index 74cd924047..2f5084b867 100644 --- a/server/public/model/custom_status.go +++ b/server/public/model/custom_status.go @@ -8,8 +8,6 @@ import ( "encoding/json" "fmt" "time" - - "github.com/graph-gophers/graphql-go" ) const ( @@ -63,12 +61,6 @@ func (cs *CustomStatus) AreDurationAndExpirationTimeValid() bool { 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 { return fmt.Sprintf("%04x", r) } diff --git a/server/public/model/feature_flags.go b/server/public/model/feature_flags.go index 3a2a470f62..bceb4daa38 100644 --- a/server/public/model/feature_flags.go +++ b/server/public/model/feature_flags.go @@ -29,9 +29,6 @@ type FeatureFlags struct { NormalizeLdapDNs bool - // Enable GraphQL feature - GraphQL bool - PostPriority bool // Enable WYSIWYG text editor @@ -54,7 +51,6 @@ func (f *FeatureFlags) SetDefaults() { f.EnableRemoteClusterService = false f.AppsEnabled = true f.NormalizeLdapDNs = false - f.GraphQL = false f.CallsEnabled = true f.DeprecateCloudFree = false f.WysiwygEditor = false diff --git a/server/public/model/status.go b/server/public/model/status.go index e362afe844..a0c5d073f3 100644 --- a/server/public/model/status.go +++ b/server/public/model/status.go @@ -34,19 +34,6 @@ func (s *Status) ToJSON() ([]byte, error) { 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) { list := make([]Status, len(u)) for i, s := range u { diff --git a/server/public/model/team.go b/server/public/model/team.go index 7643051688..f6b8ca71c4 100644 --- a/server/public/model/team.go +++ b/server/public/model/team.go @@ -287,24 +287,3 @@ func (o *Team) ShallowCopy() *Team { c := *o 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) -} diff --git a/server/public/model/team_member.go b/server/public/model/team_member.go index fbcecadd73..276777eb04 100644 --- a/server/public/model/team_member.go +++ b/server/public/model/team_member.go @@ -142,9 +142,3 @@ func (o *TeamMember) PreUpdate() { func (o *TeamMember) GetRoles() []string { 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) -} diff --git a/server/public/model/user.go b/server/public/model/user.go index daf8c77cdd..baf28be7bc 100644 --- a/server/public/model/user.go +++ b/server/public/model/user.go @@ -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. func (u *User) PreUpdate() { u.Username = SanitizeUnicode(u.Username) diff --git a/server/public/model/utils.go b/server/public/model/utils.go index 621e00428c..347cbaf09f 100644 --- a/server/public/model/utils.go +++ b/server/public/model/utils.go @@ -177,24 +177,10 @@ func (m StringMap) Value() (driver.Value, error) { return string(buf), nil } -func (StringMap) ImplementsGraphQLType(name string) bool { - return name == "StringMap" -} - func (m StringMap) MarshalJSON() ([]byte, error) { 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 { if value == nil { return nil @@ -228,24 +214,10 @@ func (si StringInterface) Value() (driver.Value, error) { return string(j), err } -func (StringInterface) ImplementsGraphQLType(name string) bool { - return name == "StringInterface" -} - func (si StringInterface) MarshalJSON() ([]byte, error) { 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 translateFuncOnce sync.Once diff --git a/server/public/pluginapi/experimental/oauther/mock_oauther/mock_oauther.go b/server/public/pluginapi/experimental/oauther/mock_oauther/mock_oauther.go index 22c1f8d594..c2253d2f11 100644 --- a/server/public/pluginapi/experimental/oauther/mock_oauther/mock_oauther.go +++ b/server/public/pluginapi/experimental/oauther/mock_oauther/mock_oauther.go @@ -5,9 +5,10 @@ package mock_oauther import ( + reflect "reflect" + gomock "github.com/golang/mock/gomock" oauth2 "golang.org/x/oauth2" - reflect "reflect" ) // MockOAuther is a mock of OAuther interface diff --git a/server/public/shared/mlog/graphql.go b/server/public/shared/mlog/graphql.go deleted file mode 100644 index 7f439f3010..0000000000 --- a/server/public/shared/mlog/graphql.go +++ /dev/null @@ -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)) -}