diff --git a/backend/src/middleware/filterBubble/filterBubble.js b/backend/src/middleware/filterBubble/filterBubble.js new file mode 100644 index 000000000..bfdad5e2c --- /dev/null +++ b/backend/src/middleware/filterBubble/filterBubble.js @@ -0,0 +1,12 @@ +import replaceParams from './replaceParams' + +const replaceFilterBubbleParams = async (resolve, root, args, context, resolveInfo) => { + args = await replaceParams(args, context) + return resolve(root, args, context, resolveInfo) +} + +export default { + Query: { + Post: replaceFilterBubbleParams, + }, +} diff --git a/backend/src/middleware/filterBubble/filterBubble.spec.js b/backend/src/middleware/filterBubble/filterBubble.spec.js new file mode 100644 index 000000000..e18654868 --- /dev/null +++ b/backend/src/middleware/filterBubble/filterBubble.spec.js @@ -0,0 +1,76 @@ +import { GraphQLClient } from 'graphql-request' +import { host, login } from '../../jest/helpers' +import Factory from '../../seed/factories' + +const factory = Factory() + +const currentUserParams = { + email: 'you@example.org', + name: 'This is you', + password: '1234', +} +const followedAuthorParams = { + id: 'u2', + email: 'followed@example.org', + name: 'Followed User', + password: '1234', +} +const randomAuthorParams = { + email: 'someone@example.org', + name: 'Someone else', + password: 'else', +} + +beforeEach(async () => { + await Promise.all([ + factory.create('User', currentUserParams), + factory.create('User', followedAuthorParams), + factory.create('User', randomAuthorParams), + ]) + const [asYourself, asFollowedUser, asSomeoneElse] = await Promise.all([ + Factory().authenticateAs(currentUserParams), + Factory().authenticateAs(followedAuthorParams), + Factory().authenticateAs(randomAuthorParams), + ]) + await asYourself.follow({ id: 'u2', type: 'User' }) + await asFollowedUser.create('Post', { title: 'This is the post of a followed user' }) + await asSomeoneElse.create('Post', { title: 'This is some random post' }) +}) + +afterEach(async () => { + await factory.cleanDatabase() +}) + +describe('FilterBubble middleware', () => { + describe('given an authenticated user', () => { + let authenticatedClient + + beforeEach(async () => { + const headers = await login(currentUserParams) + authenticatedClient = new GraphQLClient(host, { headers }) + }) + + describe('no filter bubble', () => { + it('returns all posts', async () => { + const query = '{ Post( filterBubble: {}) { title } }' + const expected = { + Post: [ + { title: 'This is some random post' }, + { title: 'This is the post of a followed user' }, + ], + } + await expect(authenticatedClient.request(query)).resolves.toEqual(expected) + }) + }) + + describe('filtering for posts of followed users only', () => { + it('returns only posts authored by followed users', async () => { + const query = '{ Post( filterBubble: { author: followed }) { title } }' + const expected = { + Post: [{ title: 'This is the post of a followed user' }], + } + await expect(authenticatedClient.request(query)).resolves.toEqual(expected) + }) + }) + }) +}) diff --git a/backend/src/middleware/index.js b/backend/src/middleware/index.js index 75314abc0..6bc7be000 100644 --- a/backend/src/middleware/index.js +++ b/backend/src/middleware/index.js @@ -13,6 +13,7 @@ import includedFields from './includedFieldsMiddleware' import orderBy from './orderByMiddleware' import validation from './validation' import notifications from './notifications' +import filterBubble from './filterBubble/filterBubble' export default schema => { const middlewares = { @@ -30,11 +31,13 @@ export default schema => { user: user, includedFields: includedFields, orderBy: orderBy, + filterBubble: filterBubble, } let order = [ 'permissions', 'activityPub', + 'filterBubble', 'password', 'dateTime', 'validation', diff --git a/backend/src/schema/types/type/Post.gql b/backend/src/schema/types/type/Post.gql index 6e23862ed..f28059366 100644 --- a/backend/src/schema/types/type/Post.gql +++ b/backend/src/schema/types/type/Post.gql @@ -1,3 +1,18 @@ +enum FilterBubbleAuthorEnum { + followed + all +} + +input FilterBubble { + author: FilterBubbleAuthorEnum +} + +type Query { + Post( + filterBubble: FilterBubble + ): [Post] +} + type Post { id: ID! activityId: String @@ -40,4 +55,4 @@ type Post { RETURN COUNT(u) >= 1 """ ) -} \ No newline at end of file +}