From 7693277d04b53ed9cd6719ae2986da90883c0c05 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Wed, 17 Aug 2022 11:31:18 +0200 Subject: [PATCH 1/4] feat: add user-by-connection query --- .../gql/v3/resolvers/query/query.users.go | 25 +++++++++++++++++++ internal/gql/v3/schema/users.gql | 1 + 2 files changed, 26 insertions(+) diff --git a/internal/gql/v3/resolvers/query/query.users.go b/internal/gql/v3/resolvers/query/query.users.go index 85da8886..4c97571e 100644 --- a/internal/gql/v3/resolvers/query/query.users.go +++ b/internal/gql/v3/resolvers/query/query.users.go @@ -2,6 +2,7 @@ package query import ( "context" + "strings" "github.com/hashicorp/go-multierror" "github.com/seventv/api/internal/gql/v3/auth" @@ -83,3 +84,27 @@ func (r *Resolver) Users(ctx context.Context, queryArg string, pageArg *int, lim return result, err } + +func (r *Resolver) UserByConnection(ctx context.Context, connectionId string) (*model.User, error) { + user, err := r.Ctx.Inst().Query.Users(ctx, bson.M{"connections.id": strings.ToLower(connectionId)}).First() + if err != nil { + return nil, err + } + + if user.ID.IsZero() || user.ID == structures.DeletedUser.ID { + return nil, errors.ErrUnknownUser() + } + + bans, err := r.Ctx.Inst().Query.Bans(ctx, query.BanQueryOptions{ // remove emotes made by users who own nothing and are happy + Filter: bson.M{"effects": bson.M{"$bitsAnySet": structures.BanEffectMemoryHole}}, + }) + if err != nil { + return nil, err + } + + if _, ok := bans.MemoryHole[user.ID]; ok { + return nil, errors.ErrUnknownUser() + } + + return helpers.UserStructureToModel(user, r.Ctx.Config().CdnURL), nil +} diff --git a/internal/gql/v3/schema/users.gql b/internal/gql/v3/schema/users.gql index 0232d065..38a1b6cf 100644 --- a/internal/gql/v3/schema/users.gql +++ b/internal/gql/v3/schema/users.gql @@ -2,6 +2,7 @@ extend type Query { actor: User user(id: ObjectID!): User! usersByID(list: [ObjectID!]!): [UserPartial!]! + userByConnection(id: String!): User! users(query: String!, page: Int, limit: Int): [UserPartial!]! } From 8c211b4cffc101fcd7d3052c304055297d7fbbbd Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Wed, 17 Aug 2022 13:54:36 +0200 Subject: [PATCH 2/4] fix: use loaded for connection-id query --- internal/gql/v3/resolvers/query/query.users.go | 2 +- internal/loaders/loaders.go | 12 ++++++++++-- internal/loaders/user.loader.go | 7 +++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/internal/gql/v3/resolvers/query/query.users.go b/internal/gql/v3/resolvers/query/query.users.go index 4c97571e..20c1ccd9 100644 --- a/internal/gql/v3/resolvers/query/query.users.go +++ b/internal/gql/v3/resolvers/query/query.users.go @@ -86,7 +86,7 @@ func (r *Resolver) Users(ctx context.Context, queryArg string, pageArg *int, lim } func (r *Resolver) UserByConnection(ctx context.Context, connectionId string) (*model.User, error) { - user, err := r.Ctx.Inst().Query.Users(ctx, bson.M{"connections.id": strings.ToLower(connectionId)}).First() + user, err := r.Ctx.Inst().Loaders.UserByConnectionID().Load(strings.ToLower(connectionId)) if err != nil { return nil, err } diff --git a/internal/loaders/loaders.go b/internal/loaders/loaders.go index fd5f3159..511bc14b 100644 --- a/internal/loaders/loaders.go +++ b/internal/loaders/loaders.go @@ -17,6 +17,7 @@ const LoadersKey = utils.Key("dataloaders") type Instance interface { UserByID() UserLoaderByID UserByUsername() UserLoaderByUsername + UserByConnectionID() UserByConnectionID EmoteByID() EmoteLoaderByID EmoteByOwnerID() BatchEmoteLoaderByID EmoteSetByID() EmoteSetLoaderByID @@ -25,8 +26,9 @@ type Instance interface { type inst struct { // User Loaders - userByID UserLoaderByID - userByUsername UserLoaderByUsername + userByID UserLoaderByID + userByUsername UserLoaderByUsername + userByConnectionID UserByConnectionID // Emote Loaders emoteByID EmoteLoaderByID @@ -52,6 +54,7 @@ func New(ctx context.Context, mngo mongo.Instance, rdis redis.Instance, quer *qu l.userByID = userLoader[primitive.ObjectID](ctx, l, "_id") l.userByUsername = userLoader[string](ctx, l, "username") + l.userByConnectionID = userLoader[string](ctx, l, "connection.id") l.emoteByID = emoteLoader(ctx, l, "versions.id") l.emoteByOwnerID = batchEmoteLoader(ctx, l, "owner_id") l.emoteSetByID = emoteSetByID(ctx, l) @@ -68,6 +71,10 @@ func (l inst) UserByUsername() UserLoaderByUsername { return l.userByUsername } +func (l inst) UserByConnectionID() UserByConnectionID { + return l.userByConnectionID +} + func (l inst) EmoteByID() EmoteLoaderByID { return l.emoteByID } @@ -88,6 +95,7 @@ func (l *inst) EmoteByOwnerID() BatchEmoteLoaderByID { type ( UserLoaderByID = *dataloader.DataLoader[primitive.ObjectID, structures.User] UserLoaderByUsername = *dataloader.DataLoader[string, structures.User] + UserByConnectionID = *dataloader.DataLoader[string, structures.User] EmoteLoaderByID = *dataloader.DataLoader[primitive.ObjectID, structures.Emote] BatchEmoteLoaderByID = *dataloader.DataLoader[primitive.ObjectID, []structures.Emote] EmoteSetLoaderByID = *dataloader.DataLoader[primitive.ObjectID, structures.EmoteSet] diff --git a/internal/loaders/user.loader.go b/internal/loaders/user.loader.go index a0794e69..1e8b8514 100644 --- a/internal/loaders/user.loader.go +++ b/internal/loaders/user.loader.go @@ -42,6 +42,13 @@ func userLoader[T comparable](ctx context.Context, x inst, keyName string) *data case "username": v, _ := utils.ToAny(u.Username).(T) m[v] = u + case "connection.id": + for _, c := range u.Connections { + if c.ID != "" { + v, _ := utils.ToAny(c.ID).(T) + m[v] = u + } + } default: v, _ := utils.ToAny(u.ID).(T) m[v] = u From 3a35803f14a3f54ada04463ae711d272fe9d5c51 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Thu, 18 Aug 2022 10:32:25 +0200 Subject: [PATCH 3/4] refactor: require users to specify a connection platform --- .../gql/v3/resolvers/query/query.users.go | 9 ++- internal/gql/v3/schema/users.gql | 2 +- internal/loaders/loaders.go | 15 +++-- internal/loaders/user.loader.go | 60 ++++++++++++++++--- 4 files changed, 70 insertions(+), 16 deletions(-) diff --git a/internal/gql/v3/resolvers/query/query.users.go b/internal/gql/v3/resolvers/query/query.users.go index 20c1ccd9..e7a1be86 100644 --- a/internal/gql/v3/resolvers/query/query.users.go +++ b/internal/gql/v3/resolvers/query/query.users.go @@ -2,7 +2,6 @@ package query import ( "context" - "strings" "github.com/hashicorp/go-multierror" "github.com/seventv/api/internal/gql/v3/auth" @@ -85,8 +84,12 @@ func (r *Resolver) Users(ctx context.Context, queryArg string, pageArg *int, lim return result, err } -func (r *Resolver) UserByConnection(ctx context.Context, connectionId string) (*model.User, error) { - user, err := r.Ctx.Inst().Loaders.UserByConnectionID().Load(strings.ToLower(connectionId)) +func (r *Resolver) UserByConnection(ctx context.Context, connectionPlatform model.ConnectionPlatform, id string) (*model.User, error) { + l, ok := r.Ctx.Inst().Loaders.UserByConnectionID(structures.UserConnectionPlatform(connectionPlatform)) + if !ok { + return nil, errors.ErrInvalidRequest().SetDetail("Unknown connection platform") + } + user, err := l.Load(id) if err != nil { return nil, err } diff --git a/internal/gql/v3/schema/users.gql b/internal/gql/v3/schema/users.gql index 38a1b6cf..e6e0022a 100644 --- a/internal/gql/v3/schema/users.gql +++ b/internal/gql/v3/schema/users.gql @@ -2,7 +2,7 @@ extend type Query { actor: User user(id: ObjectID!): User! usersByID(list: [ObjectID!]!): [UserPartial!]! - userByConnection(id: String!): User! + userByConnection(platform: ConnectionPlatform!, id: String!): User! users(query: String!, page: Int, limit: Int): [UserPartial!]! } diff --git a/internal/loaders/loaders.go b/internal/loaders/loaders.go index 511bc14b..fb2d3be8 100644 --- a/internal/loaders/loaders.go +++ b/internal/loaders/loaders.go @@ -17,7 +17,7 @@ const LoadersKey = utils.Key("dataloaders") type Instance interface { UserByID() UserLoaderByID UserByUsername() UserLoaderByUsername - UserByConnectionID() UserByConnectionID + UserByConnectionID(structures.UserConnectionPlatform) (UserByConnectionID, bool) EmoteByID() EmoteLoaderByID EmoteByOwnerID() BatchEmoteLoaderByID EmoteSetByID() EmoteSetLoaderByID @@ -28,7 +28,7 @@ type inst struct { // User Loaders userByID UserLoaderByID userByUsername UserLoaderByUsername - userByConnectionID UserByConnectionID + userByConnectionID map[structures.UserConnectionPlatform]UserByConnectionID // Emote Loaders emoteByID EmoteLoaderByID @@ -54,7 +54,11 @@ func New(ctx context.Context, mngo mongo.Instance, rdis redis.Instance, quer *qu l.userByID = userLoader[primitive.ObjectID](ctx, l, "_id") l.userByUsername = userLoader[string](ctx, l, "username") - l.userByConnectionID = userLoader[string](ctx, l, "connection.id") + l.userByConnectionID = map[structures.UserConnectionPlatform]*dataloader.DataLoader[string, structures.User]{ + structures.UserConnectionPlatformTwitch: userByConnectionLoader(ctx, l, structures.UserConnectionPlatformTwitch), + structures.UserConnectionPlatformYouTube: userByConnectionLoader(ctx, l, structures.UserConnectionPlatformYouTube), + structures.UserConnectionPlatformDiscord: userByConnectionLoader(ctx, l, structures.UserConnectionPlatformDiscord), + } l.emoteByID = emoteLoader(ctx, l, "versions.id") l.emoteByOwnerID = batchEmoteLoader(ctx, l, "owner_id") l.emoteSetByID = emoteSetByID(ctx, l) @@ -71,8 +75,9 @@ func (l inst) UserByUsername() UserLoaderByUsername { return l.userByUsername } -func (l inst) UserByConnectionID() UserByConnectionID { - return l.userByConnectionID +func (l inst) UserByConnectionID(platform structures.UserConnectionPlatform) (UserByConnectionID, bool) { + loader, ok := l.userByConnectionID[platform] + return loader, ok } func (l inst) EmoteByID() EmoteLoaderByID { diff --git a/internal/loaders/user.loader.go b/internal/loaders/user.loader.go index 1e8b8514..19789489 100644 --- a/internal/loaders/user.loader.go +++ b/internal/loaders/user.loader.go @@ -42,13 +42,6 @@ func userLoader[T comparable](ctx context.Context, x inst, keyName string) *data case "username": v, _ := utils.ToAny(u.Username).(T) m[v] = u - case "connection.id": - for _, c := range u.Connections { - if c.ID != "" { - v, _ := utils.ToAny(c.ID).(T) - m[v] = u - } - } default: v, _ := utils.ToAny(u.ID).(T) m[v] = u @@ -72,3 +65,56 @@ func userLoader[T comparable](ctx context.Context, x inst, keyName string) *data }, }) } + +func userByConnectionLoader(ctx context.Context, x inst, platform structures.UserConnectionPlatform) *dataloader.DataLoader[string, structures.User] { + return dataloader.New(dataloader.Config[string, structures.User]{ + Wait: time.Millisecond * 25, + Fetch: func(keys []string) ([]structures.User, []error) { + ctx, cancel := context.WithTimeout(ctx, time.Second*10) + defer cancel() + + items := make([]structures.User, len(keys)) + errs := make([]error, len(keys)) + + // Initially fill the response with deleted emotes in case some cannot be found + for i := 0; i < len(items); i++ { + items[i] = structures.DeletedUser + } + + // Fetch users + result := x.query.Users(ctx, bson.M{ + "connections.id": bson.M{"$in": keys}, + "connections.platform": platform, + }) + if result.Empty() { + return items, errs + } + users, err := result.Items() + + if err == nil { + m := make(map[string]structures.User) + for _, u := range users { + for _, c := range u.Connections { + if c.Platform == platform { + m[c.ID] = u + } + } + } + + for i, v := range keys { + if x, ok := m[v]; ok { + items[i] = x + } else { + errs[i] = errors.ErrUnknownUser() + } + } + } else { + for i := range errs { + errs[i] = err + } + } + + return items, errs + }, + }) +} From 2182767a414a20b1cd74293611c1de9399449f09 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Thu, 18 Aug 2022 10:43:44 +0200 Subject: [PATCH 4/4] fix: ci --- internal/gql/v3/resolvers/query/query.users.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/gql/v3/resolvers/query/query.users.go b/internal/gql/v3/resolvers/query/query.users.go index e7a1be86..6ea721be 100644 --- a/internal/gql/v3/resolvers/query/query.users.go +++ b/internal/gql/v3/resolvers/query/query.users.go @@ -89,6 +89,7 @@ func (r *Resolver) UserByConnection(ctx context.Context, connectionPlatform mode if !ok { return nil, errors.ErrInvalidRequest().SetDetail("Unknown connection platform") } + user, err := l.Load(id) if err != nil { return nil, err