mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Merge pull request #2243 from Human-Connection/refactor_neo4j-graphql-js
Explicitly define our schema, improve performance
This commit is contained in:
commit
19d2e2cd41
@ -1,12 +0,0 @@
|
||||
import {
|
||||
GraphQLLowerCaseDirective,
|
||||
GraphQLTrimDirective,
|
||||
GraphQLDefaultToDirective,
|
||||
} from 'graphql-custom-directives'
|
||||
|
||||
export default function applyDirectives(augmentedSchema) {
|
||||
const directives = [GraphQLLowerCaseDirective, GraphQLTrimDirective, GraphQLDefaultToDirective]
|
||||
augmentedSchema._directives.push.apply(augmentedSchema._directives, directives)
|
||||
|
||||
return augmentedSchema
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
import { GraphQLDate, GraphQLTime, GraphQLDateTime } from 'graphql-iso-date'
|
||||
|
||||
export default function applyScalars(augmentedSchema) {
|
||||
augmentedSchema._typeMap.Date = GraphQLDate
|
||||
augmentedSchema._typeMap.Time = GraphQLTime
|
||||
augmentedSchema._typeMap.DateTime = GraphQLDateTime
|
||||
|
||||
return augmentedSchema
|
||||
}
|
||||
@ -41,20 +41,6 @@ const isMySocialMedia = rule({
|
||||
return socialMedia.ownedBy.node.id === user.id
|
||||
})
|
||||
|
||||
/* TODO: decide if we want to remove this check: the check
|
||||
* `onlyEnabledContent` throws authorization errors only if you have
|
||||
* arguments for `disabled` or `deleted` assuming these are filter
|
||||
* parameters. Soft-delete middleware obfuscates data on its way out
|
||||
* anyways. Furthermore, `neo4j-graphql-js` offers many ways to filter for
|
||||
* data so I believe, this is not a good check anyways.
|
||||
*/
|
||||
const onlyEnabledContent = rule({
|
||||
cache: 'strict',
|
||||
})(async (parent, args, ctx, info) => {
|
||||
const { disabled, deleted } = args
|
||||
return !(disabled || deleted)
|
||||
})
|
||||
|
||||
const invitationLimitReached = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (parent, args, { user, driver }) => {
|
||||
@ -125,7 +111,8 @@ const permissions = shield(
|
||||
reports: isModerator,
|
||||
statistics: allow,
|
||||
currentUser: allow,
|
||||
Post: or(onlyEnabledContent, isModerator),
|
||||
Post: allow,
|
||||
profilePagePosts: allow,
|
||||
Comment: allow,
|
||||
User: or(noEmailFilter, isAdmin),
|
||||
isLoggedIn: allow,
|
||||
@ -134,7 +121,6 @@ const permissions = shield(
|
||||
PostsEmotionsByCurrentUser: isAuthenticated,
|
||||
blockedUsers: isAuthenticated,
|
||||
notifications: isAuthenticated,
|
||||
profilePagePosts: or(onlyEnabledContent, isModerator),
|
||||
Donations: isAuthenticated,
|
||||
},
|
||||
Mutation: {
|
||||
|
||||
@ -25,9 +25,5 @@ export default {
|
||||
args.slug = args.slug || (await uniqueSlug(args.title, isUniqueFor(context, 'Post')))
|
||||
return resolve(root, args, context, info)
|
||||
},
|
||||
CreateCategory: async (resolve, root, args, context, info) => {
|
||||
args.slug = args.slug || (await uniqueSlug(args.name, isUniqueFor(context, 'Category')))
|
||||
return resolve(root, args, context, info)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -3,9 +3,7 @@ const isModerator = ({ user }) => {
|
||||
}
|
||||
|
||||
const setDefaultFilters = (resolve, root, args, context, info) => {
|
||||
if (typeof args.deleted !== 'boolean') {
|
||||
args.deleted = false
|
||||
}
|
||||
args.deleted = false
|
||||
|
||||
if (!isModerator(context)) {
|
||||
args.disabled = false
|
||||
|
||||
@ -341,76 +341,6 @@ describe('softDeleteMiddleware', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('filter (deleted: true)', () => {
|
||||
beforeEach(() => {
|
||||
graphqlQuery = gql`
|
||||
{
|
||||
Post(deleted: true) {
|
||||
title
|
||||
}
|
||||
}
|
||||
`
|
||||
})
|
||||
|
||||
describe('as user', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await user.toJson()
|
||||
})
|
||||
|
||||
it('throws authorisation error', async () => {
|
||||
const { data, errors } = await action()
|
||||
expect(data).toEqual({ Post: null })
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorised!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('as moderator', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await moderator.toJson()
|
||||
})
|
||||
|
||||
it('does not show deleted posts', async () => {
|
||||
const expected = { data: { Post: [{ title: 'UNAVAILABLE' }] } }
|
||||
await expect(action()).resolves.toMatchObject(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('filter (disabled: true)', () => {
|
||||
beforeEach(() => {
|
||||
graphqlQuery = gql`
|
||||
{
|
||||
Post(disabled: true) {
|
||||
title
|
||||
}
|
||||
}
|
||||
`
|
||||
})
|
||||
|
||||
describe('as user', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await user.toJson()
|
||||
})
|
||||
|
||||
it('throws authorisation error', async () => {
|
||||
const { data, errors } = await action()
|
||||
expect(data).toEqual({ Post: null })
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorised!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('as moderator', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await moderator.toJson()
|
||||
})
|
||||
|
||||
it('shows disabled posts', async () => {
|
||||
const expected = { data: { Post: [{ title: 'Disabled post' }] } }
|
||||
await expect(action()).resolves.toMatchObject(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
export const undefinedToNull = list => {
|
||||
const resolvers = {}
|
||||
list.forEach(key => {
|
||||
resolvers[key] = async (parent, params, context, resolveInfo) => {
|
||||
return typeof parent[key] === 'undefined' ? null : parent[key]
|
||||
}
|
||||
})
|
||||
return resolvers
|
||||
}
|
||||
@ -1,56 +1,27 @@
|
||||
import { makeAugmentedSchema } from 'neo4j-graphql-js'
|
||||
import CONFIG from './../config'
|
||||
import applyScalars from './../bootstrap/scalars'
|
||||
import applyDirectives from './../bootstrap/directives'
|
||||
import typeDefs from './types'
|
||||
import resolvers from './resolvers'
|
||||
|
||||
export default applyScalars(
|
||||
applyDirectives(
|
||||
makeAugmentedSchema({
|
||||
typeDefs,
|
||||
resolvers,
|
||||
config: {
|
||||
query: {
|
||||
exclude: [
|
||||
'Badge',
|
||||
'Embed',
|
||||
'InvitationCode',
|
||||
'EmailAddress',
|
||||
'Notfication',
|
||||
'Statistics',
|
||||
'LoggedInUser',
|
||||
'Location',
|
||||
'SocialMedia',
|
||||
'NOTIFIED',
|
||||
'REPORTED',
|
||||
'Donations',
|
||||
],
|
||||
// add 'User' here as soon as possible
|
||||
},
|
||||
mutation: {
|
||||
exclude: [
|
||||
'Badge',
|
||||
'Embed',
|
||||
'InvitationCode',
|
||||
'EmailAddress',
|
||||
'Notfication',
|
||||
'Post',
|
||||
'Comment',
|
||||
'Statistics',
|
||||
'LoggedInUser',
|
||||
'Location',
|
||||
'SocialMedia',
|
||||
'User',
|
||||
'EMOTED',
|
||||
'NOTIFIED',
|
||||
'REPORTED',
|
||||
'Donations',
|
||||
],
|
||||
// add 'User' here as soon as possible
|
||||
},
|
||||
debug: !!CONFIG.DEBUG,
|
||||
},
|
||||
}),
|
||||
),
|
||||
)
|
||||
export default makeAugmentedSchema({
|
||||
typeDefs,
|
||||
resolvers,
|
||||
config: {
|
||||
query: {
|
||||
exclude: [
|
||||
'Badge',
|
||||
'Embed',
|
||||
'InvitationCode',
|
||||
'EmailAddress',
|
||||
'Notfication',
|
||||
'Statistics',
|
||||
'LoggedInUser',
|
||||
'Location',
|
||||
'SocialMedia',
|
||||
'NOTIFIED',
|
||||
'REPORTED',
|
||||
'Donations',
|
||||
],
|
||||
},
|
||||
mutation: false,
|
||||
},
|
||||
})
|
||||
|
||||
@ -29,7 +29,7 @@ const filterForBlockedUsers = async (params, context) => {
|
||||
}
|
||||
|
||||
const maintainPinnedPosts = params => {
|
||||
const pinnedPostFilter = { pinnedBy_in: { role_in: ['admin'] } }
|
||||
const pinnedPostFilter = { pinned: true }
|
||||
if (isEmpty(params.filter)) {
|
||||
params.filter = { OR: [pinnedPostFilter, {}] }
|
||||
} else {
|
||||
|
||||
@ -210,6 +210,7 @@ describe('Post', () => {
|
||||
data: {
|
||||
Post: expect.arrayContaining(expected),
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -229,7 +230,9 @@ describe('Post', () => {
|
||||
|
||||
await user.relateTo(followedUser, 'following')
|
||||
variables = { filter: { author: { followedBy_some: { id: 'current-user' } } } }
|
||||
const expected = {
|
||||
await expect(
|
||||
query({ query: postQueryFilteredByUsersFollowed, variables }),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
Post: [
|
||||
{
|
||||
@ -238,10 +241,8 @@ describe('Post', () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
await expect(
|
||||
query({ query: postQueryFilteredByUsersFollowed, variables }),
|
||||
).resolves.toMatchObject(expected)
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1 +0,0 @@
|
||||
scalar Date
|
||||
@ -1 +0,0 @@
|
||||
scalar DateTime
|
||||
@ -1 +0,0 @@
|
||||
scalar Time
|
||||
@ -3,8 +3,6 @@ type Badge {
|
||||
type: BadgeType!
|
||||
status: BadgeStatus!
|
||||
icon: String!
|
||||
#createdAt: DateTime
|
||||
#updatedAt: DateTime
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
|
||||
|
||||
@ -1,13 +1,41 @@
|
||||
enum _CategoryOrdering {
|
||||
id_asc
|
||||
id_desc
|
||||
name_asc
|
||||
name_desc
|
||||
slug_asc
|
||||
slug_desc
|
||||
icon_asc
|
||||
icon_desc
|
||||
createdAt_asc
|
||||
createdAt_desc
|
||||
updatedAt_asc
|
||||
updatedAt_desc
|
||||
postCount_asc
|
||||
postCount_desc
|
||||
}
|
||||
|
||||
type Category {
|
||||
id: ID!
|
||||
name: String!
|
||||
slug: String
|
||||
icon: String!
|
||||
#createdAt: DateTime
|
||||
#updatedAt: DateTime
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
|
||||
posts: [Post]! @relation(name: "CATEGORIZED", direction: "IN")
|
||||
postCount: Int! @cypher(statement: "MATCH (this)<-[:CATEGORIZED]-(r:Post) RETURN COUNT(r)")
|
||||
}
|
||||
|
||||
type Query {
|
||||
Category(
|
||||
id: ID
|
||||
name: String
|
||||
slug: String
|
||||
icon: String
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
first: Int
|
||||
offset: Int
|
||||
orderBy: [_CategoryOrdering]
|
||||
): [Category]
|
||||
}
|
||||
|
||||
@ -1,3 +1,41 @@
|
||||
enum _CommentOrdering {
|
||||
id_asc
|
||||
id_desc
|
||||
content_asc
|
||||
content_desc
|
||||
createdAt_asc
|
||||
createdAt_desc
|
||||
updatedAt_asc
|
||||
updatedAt_desc
|
||||
}
|
||||
|
||||
input _CommentFilter {
|
||||
AND: [_CommentFilter!]
|
||||
OR: [_CommentFilter!]
|
||||
id: ID
|
||||
id_not: ID
|
||||
id_in: [ID!]
|
||||
id_not_in: [ID!]
|
||||
author: _UserFilter
|
||||
author_not: _UserFilter
|
||||
author_in: [_UserFilter!]
|
||||
author_not_in: [_UserFilter!]
|
||||
content: String
|
||||
content_not: String
|
||||
content_in: [String!]
|
||||
content_not_in: [String!]
|
||||
content_contains: String
|
||||
content_not_contains: String
|
||||
content_starts_with: String
|
||||
content_not_starts_with: String
|
||||
content_ends_with: String
|
||||
content_not_ends_with: String
|
||||
post: _PostFilter
|
||||
post_not: _PostFilter
|
||||
post_in: [_PostFilter!]
|
||||
post_not_in: [_PostFilter!]
|
||||
}
|
||||
|
||||
type Comment {
|
||||
id: ID!
|
||||
activityId: String
|
||||
@ -12,6 +50,19 @@ type Comment {
|
||||
disabledBy: User @relation(name: "DISABLED", direction: "IN")
|
||||
}
|
||||
|
||||
type Query {
|
||||
Comment(
|
||||
id: ID
|
||||
content: String
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
first: Int
|
||||
offset: Int
|
||||
orderBy: [_CommentOrdering]
|
||||
filter: _CommentFilter
|
||||
): [Comment]
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
CreateComment(
|
||||
id: ID
|
||||
|
||||
@ -3,8 +3,6 @@ type EMOTED @relation(name: "EMOTED") {
|
||||
to: Post
|
||||
|
||||
emotion: Emotion
|
||||
# createdAt: DateTime
|
||||
# updatedAt: DateTime
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
}
|
||||
|
||||
@ -2,9 +2,6 @@ type InvitationCode {
|
||||
id: ID!
|
||||
token: String
|
||||
generatedBy: User @relation(name: "GENERATED", direction: "IN")
|
||||
|
||||
#createdAt: DateTime
|
||||
#usedAt: DateTime
|
||||
createdAt: String
|
||||
}
|
||||
|
||||
|
||||
@ -1,26 +1,101 @@
|
||||
input _PostFilter {
|
||||
AND: [_PostFilter!]
|
||||
OR: [_PostFilter!]
|
||||
id: ID
|
||||
id_not: ID
|
||||
id_in: [ID!]
|
||||
id_not_in: [ID!]
|
||||
author: _UserFilter
|
||||
author_not: _UserFilter
|
||||
author_in: [_UserFilter!]
|
||||
author_not_in: [_UserFilter!]
|
||||
title: String
|
||||
title_not: String
|
||||
title_in: [String!]
|
||||
title_not_in: [String!]
|
||||
title_contains: String
|
||||
title_not_contains: String
|
||||
title_starts_with: String
|
||||
title_not_starts_with: String
|
||||
title_ends_with: String
|
||||
title_not_ends_with: String
|
||||
slug: String
|
||||
slug_not: String
|
||||
slug_in: [String!]
|
||||
slug_not_in: [String!]
|
||||
slug_contains: String
|
||||
slug_not_contains: String
|
||||
slug_starts_with: String
|
||||
slug_not_starts_with: String
|
||||
slug_ends_with: String
|
||||
slug_not_ends_with: String
|
||||
content: String
|
||||
content_not: String
|
||||
content_in: [String!]
|
||||
content_not_in: [String!]
|
||||
content_contains: String
|
||||
content_not_contains: String
|
||||
content_starts_with: String
|
||||
content_not_starts_with: String
|
||||
content_ends_with: String
|
||||
content_not_ends_with: String
|
||||
image: String
|
||||
visibility: Visibility
|
||||
visibility_not: Visibility
|
||||
visibility_in: [Visibility!]
|
||||
visibility_not_in: [Visibility!]
|
||||
language: String
|
||||
language_not: String
|
||||
language_in: [String!]
|
||||
language_not_in: [String!]
|
||||
pinned: Boolean # required for `maintainPinnedPost`
|
||||
tags: _TagFilter
|
||||
tags_not: _TagFilter
|
||||
tags_in: [_TagFilter!]
|
||||
tags_not_in: [_TagFilter!]
|
||||
tags_some: _TagFilter
|
||||
tags_none: _TagFilter
|
||||
tags_single: _TagFilter
|
||||
tags_every: _TagFilter
|
||||
categories: _CategoryFilter
|
||||
categories_not: _CategoryFilter
|
||||
categories_in: [_CategoryFilter!]
|
||||
categories_not_in: [_CategoryFilter!]
|
||||
categories_some: _CategoryFilter
|
||||
categories_none: _CategoryFilter
|
||||
categories_single: _CategoryFilter
|
||||
categories_every: _CategoryFilter
|
||||
comments: _CommentFilter
|
||||
comments_not: _CommentFilter
|
||||
comments_in: [_CommentFilter!]
|
||||
comments_not_in: [_CommentFilter!]
|
||||
comments_some: _CommentFilter
|
||||
comments_none: _CommentFilter
|
||||
comments_single: _CommentFilter
|
||||
comments_every: _CommentFilter
|
||||
emotions: _PostEMOTEDFilter
|
||||
emotions_not: _PostEMOTEDFilter
|
||||
emotions_in: [_PostEMOTEDFilter!]
|
||||
emotions_not_in: [_PostEMOTEDFilter!]
|
||||
emotions_some: _PostEMOTEDFilter
|
||||
emotions_none: _PostEMOTEDFilter
|
||||
emotions_single: _PostEMOTEDFilter
|
||||
emotions_every: _PostEMOTEDFilter
|
||||
}
|
||||
|
||||
enum _PostOrdering {
|
||||
id_asc
|
||||
id_desc
|
||||
activityId_asc
|
||||
activityId_desc
|
||||
objectId_asc
|
||||
objectId_desc
|
||||
title_asc
|
||||
title_desc
|
||||
slug_asc
|
||||
slug_desc
|
||||
content_asc
|
||||
content_desc
|
||||
contentExcerpt_asc
|
||||
contentExcerpt_desc
|
||||
image_asc
|
||||
image_desc
|
||||
visibility_asc
|
||||
visibility_desc
|
||||
deleted_asc
|
||||
deleted_desc
|
||||
disabled_asc
|
||||
disabled_desc
|
||||
createdAt_asc
|
||||
createdAt_desc
|
||||
updatedAt_asc
|
||||
@ -79,7 +154,7 @@ type Post {
|
||||
@cypher(
|
||||
statement: "MATCH (this)<-[:SHOUTED]-(r:User) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)"
|
||||
)
|
||||
|
||||
|
||||
# Has the currently logged in user shouted that post?
|
||||
shoutedByCurrentUser: Boolean!
|
||||
@cypher(
|
||||
@ -128,6 +203,22 @@ type Mutation {
|
||||
}
|
||||
|
||||
type Query {
|
||||
Post(
|
||||
id: ID
|
||||
title: String
|
||||
slug: String
|
||||
content: String
|
||||
image: String
|
||||
visibility: Visibility
|
||||
pinned: Boolean
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
language: String
|
||||
first: Int
|
||||
offset: Int
|
||||
orderBy: [_PostOrdering]
|
||||
filter: _PostFilter
|
||||
): [Post]
|
||||
PostsEmotionsCountByEmotion(postId: ID!, data: _EMOTEDInput!): Int!
|
||||
PostsEmotionsByCurrentUser(postId: ID!): [String]
|
||||
profilePagePosts(filter: _PostFilter, first: Int, offset: Int, orderBy: [_PostOrdering]): [Post]
|
||||
|
||||
@ -1,3 +1,20 @@
|
||||
input _TagFilter {
|
||||
AND: [_TagFilter!]
|
||||
OR: [_TagFilter!]
|
||||
id: ID
|
||||
id_not: ID
|
||||
id_in: [ID!]
|
||||
id_not_in: [ID!]
|
||||
taggedPosts: _PostFilter
|
||||
taggedPosts_not: _PostFilter
|
||||
taggedPosts_in: [_PostFilter!]
|
||||
taggedPosts_not_in: [_PostFilter!]
|
||||
taggedPosts_some: _PostFilter
|
||||
taggedPosts_none: _PostFilter
|
||||
taggedPosts_single: _PostFilter
|
||||
taggedPosts_every: _PostFilter
|
||||
}
|
||||
|
||||
type Tag {
|
||||
id: ID!
|
||||
taggedPosts: [Post]! @relation(name: "TAGGED", direction: "IN")
|
||||
@ -6,3 +23,22 @@ type Tag {
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
}
|
||||
|
||||
enum _TagOrdering {
|
||||
id_asc
|
||||
id_desc
|
||||
taggedCount_asc
|
||||
taggedCount_desc
|
||||
taggedCountUnique_asc
|
||||
taggedCountUnique_desc
|
||||
}
|
||||
|
||||
type Query {
|
||||
Tag(
|
||||
id: ID
|
||||
first: Int
|
||||
offset: Int
|
||||
orderBy: [_TagOrdering]
|
||||
filter: _TagFilter
|
||||
): [Tag]
|
||||
}
|
||||
|
||||
@ -1,3 +1,28 @@
|
||||
enum _UserOrdering {
|
||||
id_asc
|
||||
id_desc
|
||||
name_asc
|
||||
name_desc
|
||||
slug_asc
|
||||
slug_desc
|
||||
avatar_asc
|
||||
avatar_desc
|
||||
coverImg_asc
|
||||
coverImg_desc
|
||||
role_asc
|
||||
role_desc
|
||||
locationName_asc
|
||||
locationName_desc
|
||||
about_asc
|
||||
about_desc
|
||||
createdAt_asc
|
||||
createdAt_desc
|
||||
updatedAt_asc
|
||||
updatedAt_desc
|
||||
locale_asc
|
||||
locale_desc
|
||||
}
|
||||
|
||||
type User {
|
||||
id: ID!
|
||||
actorId: String
|
||||
@ -92,12 +117,6 @@ input _UserFilter {
|
||||
id_not: ID
|
||||
id_in: [ID!]
|
||||
id_not_in: [ID!]
|
||||
id_contains: ID
|
||||
id_not_contains: ID
|
||||
id_starts_with: ID
|
||||
id_not_starts_with: ID
|
||||
id_ends_with: ID
|
||||
id_not_ends_with: ID
|
||||
friends: _UserFilter
|
||||
friends_not: _UserFilter
|
||||
friends_in: [_UserFilter!]
|
||||
@ -128,8 +147,7 @@ input _UserFilter {
|
||||
type Query {
|
||||
User(
|
||||
id: ID
|
||||
email: String
|
||||
actorId: String
|
||||
email: String # admins need to search for a user sometimes
|
||||
name: String
|
||||
slug: String
|
||||
avatar: String
|
||||
@ -139,14 +157,6 @@ type Query {
|
||||
about: String
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
friendsCount: Int
|
||||
followingCount: Int
|
||||
followedByCount: Int
|
||||
followedByCurrentUser: Boolean
|
||||
contributionsCount: Int
|
||||
commentedCount: Int
|
||||
shoutedCount: Int
|
||||
badgesCount: Int
|
||||
first: Int
|
||||
offset: Int
|
||||
orderBy: [_UserOrdering]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user