GraphQL (part 2): Model changes and resolvers (#19486)

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
```
Этот коммит содержится в:
Agniva De Sarker
2022-02-11 12:37:05 +05:30
коммит произвёл GitHub
родитель 88968f9e17
Коммит 3f52bd197a
93 изменённых файлов: 9479 добавлений и 22 удалений

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

@@ -6,6 +6,8 @@ package model
import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"errors"
"io"
"net/http"
"sort"
@@ -159,6 +161,23 @@ 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) DeepCopy() *Channel {
copy := *o
if copy.SchemeId != nil {
@@ -305,6 +324,24 @@ 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 interface{}) 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

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

@@ -60,6 +60,31 @@ type ChannelMember struct {
ExplicitRoles string `json:"explicit_roles"`
}
// 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) LastUpdateAt_() float64 {
return float64(o.LastUpdateAt)
}
// ChannelMemberWithTeamData contains ChannelMember appended with extra team information
// as well.
type ChannelMemberWithTeamData struct {

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

@@ -4,6 +4,8 @@
package model
import (
"encoding/json"
"errors"
"regexp"
)
@@ -54,6 +56,10 @@ type SidebarCategoryWithChannels struct {
Channels []string `json:"channel_ids"`
}
func (sc SidebarCategoryWithChannels) ChannelIds() []string {
return sc.Channels
}
type SidebarCategoryOrder []string
// OrderedSidebarCategories combines categories, their channel IDs and an array of Category IDs, sorted
@@ -83,3 +89,39 @@ func IsValidCategoryId(s string) bool {
// Or default categories can follow the pattern {type}_{userID}_{teamID}
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 interface{}) 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 interface{}) error {
chType, ok := input.(string)
if !ok {
return errors.New("wrong type")
}
*t = SidebarCategorySorting(chType)
return nil
}

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

@@ -46,6 +46,7 @@ const (
APIURLSuffixV1 = "/api/v1"
APIURLSuffixV4 = "/api/v4"
APIURLSuffixV5 = "/api/v5"
APIURLSuffix = APIURLSuffixV4
)

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

@@ -8,6 +8,8 @@ import (
"encoding/json"
"fmt"
"time"
"github.com/graph-gophers/graphql-go"
)
const (
@@ -61,6 +63,12 @@ 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)
}

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

@@ -69,6 +69,9 @@ type FeatureFlags struct {
// Enable Workspace optimization dashboard
WorkspaceOptimizationDashboard bool
// Enable GraphQL feature
GraphQL bool
}
func (f *FeatureFlags) SetDefaults() {
@@ -93,6 +96,7 @@ func (f *FeatureFlags) SetDefaults() {
f.BoardsDataRetention = false
f.NormalizeLdapDNs = false
f.WorkspaceOptimizationDashboard = false
f.GraphQL = false
}
func (f *FeatureFlags) Plugins() map[string]string {

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

@@ -34,6 +34,19 @@ 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 {

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

@@ -125,3 +125,9 @@ 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)
}

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

@@ -409,6 +409,23 @@ 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) LastPictureUpdateAt() float64 {
return float64(u.LastPictureUpdate)
}
// PreUpdate should be run before updating the user in the db.
func (u *User) PreUpdate() {
u.Username = SanitizeUnicode(u.Username)
@@ -630,6 +647,15 @@ func (u *User) GetCustomStatus() *CustomStatus {
return o
}
func (u *User) CustomStatus() *CustomStatus {
var o *CustomStatus
data := u.Props[UserPropsKeyCustomStatus]
_ = json.Unmarshal([]byte(data), &o)
return o
}
func (u *User) ClearCustomStatus() {
u.MakeNonNil()
u.Props[UserPropsKeyCustomStatus] = ""

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

@@ -132,6 +132,24 @@ func (m StringMap) Value() (driver.Value, error) {
return string(j), err
}
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 interface{}) error {
json, ok := input.(map[string]string)
if !ok {
return errors.New("wrong type")
}
*m = json
return nil
}
func (si *StringInterface) Scan(value interface{}) error {
if value == nil {
return nil