mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
This PR adds the necessary resolvers to support the graphQL front-end. Some additional methods need to be added to the model structs to support this. Comments have been made to clarify their purpose. There is a slight duplication of code in the api layer. But eventually that's going to go away when we move away from the REST API entirely. The schema is in api4/schema.graphqls and gets compiled into the binary as an asset. The GraphiQL editor url is at GET /api/v5/graphql. Everything is behind the feature flag MM_FEATUREFLAGS_GRAPHQL. ```release-note NONE ```
287 lines
7.4 KiB
Go
287 lines
7.4 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package api4
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/mattermost/mattermost-server/v6/app"
|
|
"github.com/mattermost/mattermost-server/v6/model"
|
|
"github.com/mattermost/mattermost-server/v6/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(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(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.LimitedClientConfigWithComputed(), nil
|
|
}
|
|
return c.App.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
|
|
}) ([]*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.Session().UserId, args.UserID)
|
|
if appErr != nil {
|
|
return nil, appErr
|
|
}
|
|
|
|
if !canSee {
|
|
c.SetPermissionError(model.PermissionViewMembers)
|
|
return nil, c.Err
|
|
}
|
|
|
|
if args.TeamID != "" {
|
|
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), args.TeamID, model.PermissionViewTeam) {
|
|
c.SetPermissionError(model.PermissionViewTeam)
|
|
return nil, c.Err
|
|
}
|
|
|
|
tm, appErr2 := c.App.GetTeamMember(args.TeamID, args.UserID)
|
|
if appErr2 != nil {
|
|
return nil, appErr2
|
|
}
|
|
|
|
return []*teamMember{{*tm}}, nil
|
|
}
|
|
|
|
members, appErr := c.App.GetTeamMembersForUser(args.UserID)
|
|
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
|
|
ChannelID string
|
|
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.Session(), args.ChannelID, model.PermissionReadChannel) {
|
|
c.SetPermissionError(model.PermissionReadChannel)
|
|
return nil, c.Err
|
|
}
|
|
|
|
member, appErr := c.App.GetChannelMember(app.WithMaster(context.Background()), 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)
|
|
}
|
|
}
|
|
|
|
members, err := c.App.Srv().Store.Channel().GetMembersForUserWithCursor(args.UserID, afterChannel, afterUser, limit, int(args.LastUpdateAt))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res := make([]*channelMember, 0, len(members))
|
|
for _, cm := range members {
|
|
res = append(res, &channelMember{cm})
|
|
}
|
|
|
|
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(ctxKey{}).(*web.Context)
|
|
if !ok {
|
|
return nil, errors.New("no web.Context found in context")
|
|
}
|
|
return c, nil
|
|
}
|