mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge pull request #1209 from Human-Connection/1054-blocked-users
🍰 1054 blocked users
This commit is contained in:
commit
ab6cd501fe
@ -5,11 +5,12 @@ const notify = async (postId, idsOfMentionedUsers, context) => {
|
||||
const session = context.driver.session()
|
||||
const createdAt = new Date().toISOString()
|
||||
const cypher = `
|
||||
match(u:User) where u.id in $idsOfMentionedUsers
|
||||
match(p:Post) where p.id = $postId
|
||||
create(n:Notification{id: apoc.create.uuid(), read: false, createdAt: $createdAt})
|
||||
merge (n)-[:NOTIFIED]->(u)
|
||||
merge (p)-[:NOTIFIED]->(n)
|
||||
MATCH(p:Post {id: $postId})<-[:WROTE]-(author:User)
|
||||
MATCH(u:User)
|
||||
WHERE u.id in $idsOfMentionedUsers
|
||||
AND NOT (u)<-[:BLOCKED]-(author)
|
||||
CREATE(n:Notification{id: apoc.create.uuid(), read: false, createdAt: $createdAt})
|
||||
MERGE (p)-[:NOTIFIED]->(n)-[:NOTIFIED]->(u)
|
||||
`
|
||||
await session.run(cypher, {
|
||||
idsOfMentionedUsers,
|
||||
|
||||
@ -1,12 +1,36 @@
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import { host, login, gql } from '../../jest/helpers'
|
||||
import { gql } from '../../jest/helpers'
|
||||
import Factory from '../../seed/factories'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import { neode, getDriver } from '../../bootstrap/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
const factory = Factory()
|
||||
let client
|
||||
const driver = getDriver()
|
||||
const instance = neode()
|
||||
let server
|
||||
let query
|
||||
let mutate
|
||||
let user
|
||||
let authenticatedUser
|
||||
|
||||
beforeAll(() => {
|
||||
const createServerResult = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
user: authenticatedUser,
|
||||
neode: instance,
|
||||
driver,
|
||||
}
|
||||
},
|
||||
})
|
||||
server = createServerResult.server
|
||||
const createTestClientResult = createTestClient(server)
|
||||
query = createTestClientResult.query
|
||||
mutate = createTestClientResult.mutate
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await factory.create('User', {
|
||||
user = await instance.create('User', {
|
||||
id: 'you',
|
||||
name: 'Al Capone',
|
||||
slug: 'al-capone',
|
||||
@ -19,8 +43,8 @@ afterEach(async () => {
|
||||
await factory.cleanDatabase()
|
||||
})
|
||||
|
||||
describe('currentUser { notifications }', () => {
|
||||
const query = gql`
|
||||
describe('notifications', () => {
|
||||
const notificationQuery = gql`
|
||||
query($read: Boolean) {
|
||||
currentUser {
|
||||
notifications(read: $read, orderBy: createdAt_desc) {
|
||||
@ -34,82 +58,60 @@ describe('currentUser { notifications }', () => {
|
||||
`
|
||||
|
||||
describe('authenticated', () => {
|
||||
let headers
|
||||
beforeEach(async () => {
|
||||
headers = await login({
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
})
|
||||
client = new GraphQLClient(host, {
|
||||
headers,
|
||||
})
|
||||
authenticatedUser = user
|
||||
})
|
||||
|
||||
describe('given another user', () => {
|
||||
let authorClient
|
||||
let authorParams
|
||||
let authorHeaders
|
||||
|
||||
let author
|
||||
beforeEach(async () => {
|
||||
authorParams = {
|
||||
author = await instance.create('User', {
|
||||
email: 'author@example.org',
|
||||
password: '1234',
|
||||
id: 'author',
|
||||
}
|
||||
await factory.create('User', authorParams)
|
||||
authorHeaders = await login(authorParams)
|
||||
})
|
||||
})
|
||||
|
||||
describe('who mentions me in a post', () => {
|
||||
let post
|
||||
const title = 'Mentioning Al Capone'
|
||||
const content =
|
||||
'Hey <a class="mention" data-mention-id="you" href="/profile/you/al-capone">@al-capone</a> how do you do?'
|
||||
|
||||
beforeEach(async () => {
|
||||
const createPostAction = async () => {
|
||||
const createPostMutation = gql`
|
||||
mutation($title: String!, $content: String!) {
|
||||
CreatePost(title: $title, content: $content) {
|
||||
mutation($id: ID, $title: String!, $content: String!) {
|
||||
CreatePost(id: $id, title: $title, content: $content) {
|
||||
id
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
authorClient = new GraphQLClient(host, {
|
||||
headers: authorHeaders,
|
||||
authenticatedUser = await author.toJson()
|
||||
await mutate({
|
||||
mutation: createPostMutation,
|
||||
variables: { id: 'p47', title, content },
|
||||
})
|
||||
const { CreatePost } = await authorClient.request(createPostMutation, {
|
||||
title,
|
||||
content,
|
||||
})
|
||||
post = CreatePost
|
||||
})
|
||||
authenticatedUser = await user.toJson()
|
||||
}
|
||||
|
||||
it('sends you a notification', async () => {
|
||||
await createPostAction()
|
||||
const expectedContent =
|
||||
'Hey <a class="mention" data-mention-id="you" href="/profile/you/al-capone" target="_blank">@al-capone</a> how do you do?'
|
||||
const expected = {
|
||||
currentUser: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
post: {
|
||||
content: expectedContent,
|
||||
},
|
||||
},
|
||||
],
|
||||
const expected = expect.objectContaining({
|
||||
data: {
|
||||
currentUser: { notifications: [{ read: false, post: { content: expectedContent } }] },
|
||||
},
|
||||
}
|
||||
})
|
||||
const { query } = createTestClient(server)
|
||||
await expect(
|
||||
client.request(query, {
|
||||
read: false,
|
||||
}),
|
||||
query({ query: notificationQuery, variables: { read: false } }),
|
||||
).resolves.toEqual(expected)
|
||||
})
|
||||
|
||||
describe('who mentions me many times', () => {
|
||||
beforeEach(async () => {
|
||||
const updatePostAction = async () => {
|
||||
const updatedContent = `
|
||||
One more mention to
|
||||
<a data-mention-id="you" class="mention" href="/profile/you">
|
||||
@ -132,41 +134,52 @@ describe('currentUser { notifications }', () => {
|
||||
}
|
||||
}
|
||||
`
|
||||
authorClient = new GraphQLClient(host, {
|
||||
headers: authorHeaders,
|
||||
authenticatedUser = await author.toJson()
|
||||
await mutate({
|
||||
mutation: updatePostMutation,
|
||||
variables: {
|
||||
id: 'p47',
|
||||
title,
|
||||
content: updatedContent,
|
||||
},
|
||||
})
|
||||
await authorClient.request(updatePostMutation, {
|
||||
id: post.id,
|
||||
title: post.title,
|
||||
content: updatedContent,
|
||||
})
|
||||
})
|
||||
authenticatedUser = await user.toJson()
|
||||
}
|
||||
|
||||
it('creates exactly one more notification', async () => {
|
||||
await createPostAction()
|
||||
await updatePostAction()
|
||||
const expectedContent =
|
||||
'<br>One more mention to<br><a data-mention-id="you" class="mention" href="/profile/you" target="_blank"><br>@al-capone<br></a><br>and again:<br><a data-mention-id="you" class="mention" href="/profile/you" target="_blank"><br>@al-capone<br></a><br>and again<br><a data-mention-id="you" class="mention" href="/profile/you" target="_blank"><br>@al-capone<br></a><br>'
|
||||
const expected = {
|
||||
currentUser: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
post: {
|
||||
content: expectedContent,
|
||||
},
|
||||
},
|
||||
{
|
||||
read: false,
|
||||
post: {
|
||||
content: expectedContent,
|
||||
},
|
||||
},
|
||||
],
|
||||
const expected = expect.objectContaining({
|
||||
data: {
|
||||
currentUser: {
|
||||
notifications: [
|
||||
{ read: false, post: { content: expectedContent } },
|
||||
{ read: false, post: { content: expectedContent } },
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
await expect(
|
||||
client.request(query, {
|
||||
read: false,
|
||||
}),
|
||||
query({ query: notificationQuery, variables: { read: false } }),
|
||||
).resolves.toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('but the author of the post blocked me', () => {
|
||||
beforeEach(async () => {
|
||||
await author.relateTo(user, 'blocked')
|
||||
})
|
||||
|
||||
it('sends no notification', async () => {
|
||||
await createPostAction()
|
||||
const expected = expect.objectContaining({
|
||||
data: { currentUser: { notifications: [] } },
|
||||
})
|
||||
const { query } = createTestClient(server)
|
||||
await expect(
|
||||
query({ query: notificationQuery, variables: { read: false } }),
|
||||
).resolves.toEqual(expected)
|
||||
})
|
||||
})
|
||||
@ -204,46 +217,40 @@ describe('Hashtags', () => {
|
||||
`
|
||||
|
||||
describe('authenticated', () => {
|
||||
let headers
|
||||
beforeEach(async () => {
|
||||
headers = await login({
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
})
|
||||
client = new GraphQLClient(host, {
|
||||
headers,
|
||||
})
|
||||
authenticatedUser = await user.toJson()
|
||||
})
|
||||
|
||||
describe('create a Post with Hashtags', () => {
|
||||
beforeEach(async () => {
|
||||
await client.request(createPostMutation, {
|
||||
postId,
|
||||
postTitle,
|
||||
postContent,
|
||||
await mutate({
|
||||
mutation: createPostMutation,
|
||||
variables: {
|
||||
postId,
|
||||
postTitle,
|
||||
postContent,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('both Hashtags are created with the "id" set to thier "name"', async () => {
|
||||
it('both Hashtags are created with the "id" set to their "name"', async () => {
|
||||
const expected = [
|
||||
{
|
||||
id: 'Democracy',
|
||||
name: 'Democracy',
|
||||
},
|
||||
{
|
||||
id: 'Liberty',
|
||||
name: 'Liberty',
|
||||
},
|
||||
{ id: 'Democracy', name: 'Democracy' },
|
||||
{ id: 'Liberty', name: 'Liberty' },
|
||||
]
|
||||
await expect(
|
||||
client.request(postWithHastagsQuery, postWithHastagsVariables),
|
||||
).resolves.toEqual({
|
||||
Post: [
|
||||
{
|
||||
tags: expect.arrayContaining(expected),
|
||||
query({ query: postWithHastagsQuery, variables: postWithHastagsVariables }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
Post: [
|
||||
{
|
||||
tags: expect.arrayContaining(expected),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('afterwards update the Post by removing a Hashtag, leaving a Hashtag and add a Hashtag', () => {
|
||||
@ -261,31 +268,28 @@ describe('Hashtags', () => {
|
||||
`
|
||||
|
||||
it('only one previous Hashtag and the new Hashtag exists', async () => {
|
||||
await client.request(updatePostMutation, {
|
||||
postId,
|
||||
postTitle,
|
||||
updatedPostContent,
|
||||
await mutate({
|
||||
mutation: updatePostMutation,
|
||||
variables: {
|
||||
postId,
|
||||
postTitle,
|
||||
updatedPostContent,
|
||||
},
|
||||
})
|
||||
|
||||
const expected = [
|
||||
{
|
||||
id: 'Elections',
|
||||
name: 'Elections',
|
||||
},
|
||||
{
|
||||
id: 'Liberty',
|
||||
name: 'Liberty',
|
||||
},
|
||||
{ id: 'Elections', name: 'Elections' },
|
||||
{ id: 'Liberty', name: 'Liberty' },
|
||||
]
|
||||
await expect(
|
||||
client.request(postWithHastagsQuery, postWithHastagsVariables),
|
||||
).resolves.toEqual({
|
||||
Post: [
|
||||
{
|
||||
tags: expect.arrayContaining(expected),
|
||||
query({ query: postWithHastagsQuery, variables: postWithHastagsVariables }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
Post: [{ tags: expect.arrayContaining(expected) }],
|
||||
},
|
||||
],
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -3,11 +3,13 @@ import cheerio from 'cheerio'
|
||||
export default function(content) {
|
||||
if (!content) return []
|
||||
const $ = cheerio.load(content)
|
||||
let userIds = $('a.mention[data-mention-id]')
|
||||
const userIds = $('a.mention[data-mention-id]')
|
||||
.map((_, el) => {
|
||||
return $(el).attr('data-mention-id')
|
||||
})
|
||||
.get()
|
||||
userIds = userIds.map(id => id.trim()).filter(id => !!id)
|
||||
return userIds
|
||||
.map(id => id.trim())
|
||||
.filter(id => !!id)
|
||||
.filter((id, index, allIds) => allIds.indexOf(id) === index)
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@ const contentEmptyMentions =
|
||||
'<p>Something inspirational about <a href="/profile/u2" data-mention-id="" target="_blank">@bob-der-baumeister</a> and <a href="/profile/u3/jenny-rostock" class="mention" data-mention-id target="_blank">@jenny-rostock</a>.</p>'
|
||||
const contentWithPlainLinks =
|
||||
'<p>Something inspirational about <a class="mention" href="/profile/u2" target="_blank">@bob-der-baumeister</a> and <a href="/profile/u3" target="_blank">@jenny-rostock</a>.</p>'
|
||||
const contentWithDuplicateIds =
|
||||
'One more mention to <a data-mention-id="you" class="mention" href="/profile/you"> @al-capone </a> and again: <a data-mention-id="you" class="mention" href="/profile/you"> @al-capone </a> and again <a data-mention-id="you" class="mention" href="/profile/you"> @al-capone </a>'
|
||||
|
||||
describe('extractMentionedUsers', () => {
|
||||
describe('content undefined', () => {
|
||||
@ -18,6 +20,10 @@ describe('extractMentionedUsers', () => {
|
||||
expect(extractMentionedUsers(contentWithPlainLinks)).toEqual([])
|
||||
})
|
||||
|
||||
it('removes duplicates', () => {
|
||||
expect(extractMentionedUsers(contentWithDuplicateIds)).toEqual(['you'])
|
||||
})
|
||||
|
||||
describe('given a link with .mention class and `data-mention-id` attribute ', () => {
|
||||
it('extracts ids', () => {
|
||||
expect(extractMentionedUsers(contentWithMentions)).toEqual(['u3'])
|
||||
|
||||
@ -1,22 +1,29 @@
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import Factory from '../seed/factories'
|
||||
import { host } from '../jest/helpers'
|
||||
import { gql } from '../jest/helpers'
|
||||
import { neode as getNeode, getDriver } from '../bootstrap/neo4j'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../server'
|
||||
|
||||
let client
|
||||
let headers
|
||||
let query
|
||||
const factory = Factory()
|
||||
const neode = getNeode()
|
||||
const driver = getDriver()
|
||||
|
||||
const { server } = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
user: null,
|
||||
neode,
|
||||
driver,
|
||||
}
|
||||
},
|
||||
})
|
||||
const { query } = createTestClient(server)
|
||||
|
||||
beforeEach(async () => {
|
||||
const userParams = { name: 'Author', email: 'author@example.org', password: '1234' }
|
||||
await factory.create('User', userParams)
|
||||
await factory.authenticateAs(userParams)
|
||||
await factory.create('Post', { title: 'first' })
|
||||
await factory.create('Post', { title: 'second' })
|
||||
await factory.create('Post', { title: 'third' })
|
||||
await factory.create('Post', { title: 'last' })
|
||||
headers = {}
|
||||
client = new GraphQLClient(host, { headers })
|
||||
await neode.create('Post', { title: 'first' })
|
||||
await neode.create('Post', { title: 'second' })
|
||||
await neode.create('Post', { title: 'third' })
|
||||
await neode.create('Post', { title: 'last' })
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
@ -25,10 +32,6 @@ afterEach(async () => {
|
||||
|
||||
describe('Query', () => {
|
||||
describe('Post', () => {
|
||||
beforeEach(() => {
|
||||
query = '{ Post { title } }'
|
||||
})
|
||||
|
||||
describe('orderBy', () => {
|
||||
it('createdAt descending is default', async () => {
|
||||
const posts = [
|
||||
@ -37,15 +40,21 @@ describe('Query', () => {
|
||||
{ title: 'second' },
|
||||
{ title: 'first' },
|
||||
]
|
||||
const expected = { Post: posts }
|
||||
await expect(client.request(query)).resolves.toEqual(expected)
|
||||
const expected = expect.objectContaining({ data: { Post: posts } })
|
||||
await expect(
|
||||
query({
|
||||
query: gql`
|
||||
{
|
||||
Post {
|
||||
title
|
||||
}
|
||||
}
|
||||
`,
|
||||
}),
|
||||
).resolves.toEqual(expected)
|
||||
})
|
||||
|
||||
describe('(orderBy: createdAt_asc)', () => {
|
||||
beforeEach(() => {
|
||||
query = '{ Post(orderBy: createdAt_asc) { title } }'
|
||||
})
|
||||
|
||||
it('orders by createdAt ascending', async () => {
|
||||
const posts = [
|
||||
{ title: 'first' },
|
||||
@ -53,8 +62,18 @@ describe('Query', () => {
|
||||
{ title: 'third' },
|
||||
{ title: 'last' },
|
||||
]
|
||||
const expected = { Post: posts }
|
||||
await expect(client.request(query)).resolves.toEqual(expected)
|
||||
const expected = expect.objectContaining({ data: { Post: posts } })
|
||||
await expect(
|
||||
query({
|
||||
query: gql`
|
||||
{
|
||||
Post(orderBy: createdAt_asc) {
|
||||
title
|
||||
}
|
||||
}
|
||||
`,
|
||||
}),
|
||||
).resolves.toEqual(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -10,7 +10,7 @@ const instance = neode()
|
||||
const isAuthenticated = rule({
|
||||
cache: 'contextual',
|
||||
})(async (_parent, _args, ctx, _info) => {
|
||||
return ctx.user != null
|
||||
return !!(ctx && ctx.user && ctx.user.id)
|
||||
})
|
||||
|
||||
const isModerator = rule()(async (parent, args, { user }, info) => {
|
||||
@ -159,6 +159,7 @@ const permissions = shield(
|
||||
Badge: allow,
|
||||
PostsEmotionsCountByEmotion: allow,
|
||||
PostsEmotionsByCurrentUser: allow,
|
||||
blockedUsers: isAuthenticated,
|
||||
},
|
||||
Mutation: {
|
||||
'*': deny,
|
||||
@ -195,6 +196,8 @@ const permissions = shield(
|
||||
resetPassword: allow,
|
||||
AddPostEmotions: isAuthenticated,
|
||||
RemovePostEmotions: isAuthenticated,
|
||||
block: isAuthenticated,
|
||||
unblock: isAuthenticated,
|
||||
},
|
||||
User: {
|
||||
email: isMyOwn,
|
||||
|
||||
19
backend/src/models/Notification.js
Normal file
19
backend/src/models/Notification.js
Normal file
@ -0,0 +1,19 @@
|
||||
import uuid from 'uuid/v4'
|
||||
|
||||
module.exports = {
|
||||
id: { type: 'uuid', primary: true, default: uuid },
|
||||
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||
read: { type: 'boolean', default: false },
|
||||
user: {
|
||||
type: 'relationship',
|
||||
relationship: 'NOTIFIED',
|
||||
target: 'User',
|
||||
direction: 'out',
|
||||
},
|
||||
post: {
|
||||
type: 'relationship',
|
||||
relationship: 'NOTIFIED',
|
||||
target: 'Post',
|
||||
direction: 'in',
|
||||
},
|
||||
}
|
||||
@ -71,4 +71,16 @@ module.exports = {
|
||||
eager: true,
|
||||
cascade: true,
|
||||
},
|
||||
blocked: {
|
||||
type: 'relationship',
|
||||
relationship: 'BLOCKED',
|
||||
target: 'User',
|
||||
direction: 'out',
|
||||
},
|
||||
notifications: {
|
||||
type: 'relationship',
|
||||
relationship: 'NOTIFIED',
|
||||
target: 'Notification',
|
||||
direction: 'in',
|
||||
},
|
||||
}
|
||||
|
||||
@ -7,4 +7,5 @@ export default {
|
||||
EmailAddress: require('./EmailAddress.js'),
|
||||
SocialMedia: require('./SocialMedia.js'),
|
||||
Post: require('./Post.js'),
|
||||
Notification: require('./Notification.js'),
|
||||
}
|
||||
|
||||
@ -15,10 +15,12 @@ export default function Resolver(type, options = {}) {
|
||||
const {
|
||||
idAttribute = 'id',
|
||||
undefinedToNull = [],
|
||||
boolean = {},
|
||||
count = {},
|
||||
hasOne = {},
|
||||
hasMany = {},
|
||||
} = options
|
||||
|
||||
const _hasResolver = (resolvers, { key, connection }, { returnType }) => {
|
||||
return async (parent, params, context, resolveInfo) => {
|
||||
if (typeof parent[key] !== 'undefined') return parent[key]
|
||||
@ -31,6 +33,26 @@ export default function Resolver(type, options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
const booleanResolver = obj => {
|
||||
const resolvers = {}
|
||||
for (const [key, condition] of Object.entries(obj)) {
|
||||
resolvers[key] = async (parent, params, { cypherParams }, resolveInfo) => {
|
||||
if (typeof parent[key] !== 'undefined') return parent[key]
|
||||
const result = await instance.cypher(
|
||||
`
|
||||
${condition.replace('this', 'this {id: $parent.id}')} as ${key}`,
|
||||
{
|
||||
parent,
|
||||
cypherParams,
|
||||
},
|
||||
)
|
||||
const [record] = result.records
|
||||
return record.get(key)
|
||||
}
|
||||
}
|
||||
return resolvers
|
||||
}
|
||||
|
||||
const countResolver = obj => {
|
||||
const resolvers = {}
|
||||
for (const [key, connection] of Object.entries(obj)) {
|
||||
@ -67,6 +89,7 @@ export default function Resolver(type, options = {}) {
|
||||
}
|
||||
const result = {
|
||||
...undefinedToNullResolver(undefinedToNull),
|
||||
...booleanResolver(boolean),
|
||||
...countResolver(count),
|
||||
...hasOneResolver(hasOne),
|
||||
...hasManyResolver(hasMany),
|
||||
|
||||
@ -1,7 +1,74 @@
|
||||
import uuid from 'uuid/v4'
|
||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
import fileUpload from './fileUpload'
|
||||
import { getBlockedUsers, getBlockedByUsers } from './users.js'
|
||||
import { mergeWith, isArray } from 'lodash'
|
||||
|
||||
const filterForBlockedUsers = async (params, context) => {
|
||||
if (!context.user) return params
|
||||
const [blockedUsers, blockedByUsers] = await Promise.all([
|
||||
getBlockedUsers(context),
|
||||
getBlockedByUsers(context),
|
||||
])
|
||||
const badIds = [...blockedByUsers.map(b => b.id), ...blockedUsers.map(b => b.id)]
|
||||
params.filter = mergeWith(
|
||||
params.filter,
|
||||
{
|
||||
author_not: { id_in: badIds },
|
||||
},
|
||||
(objValue, srcValue) => {
|
||||
if (isArray(objValue)) {
|
||||
return objValue.concat(srcValue)
|
||||
}
|
||||
},
|
||||
)
|
||||
return params
|
||||
}
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
Post: async (object, params, context, resolveInfo) => {
|
||||
params = await filterForBlockedUsers(params, context)
|
||||
return neo4jgraphql(object, params, context, resolveInfo, false)
|
||||
},
|
||||
findPosts: async (object, params, context, resolveInfo) => {
|
||||
params = await filterForBlockedUsers(params, context)
|
||||
return neo4jgraphql(object, params, context, resolveInfo, false)
|
||||
},
|
||||
PostsEmotionsCountByEmotion: async (object, params, context, resolveInfo) => {
|
||||
const session = context.driver.session()
|
||||
const { postId, data } = params
|
||||
const transactionRes = await session.run(
|
||||
`MATCH (post:Post {id: $postId})<-[emoted:EMOTED {emotion: $data.emotion}]-()
|
||||
RETURN COUNT(DISTINCT emoted) as emotionsCount
|
||||
`,
|
||||
{ postId, data },
|
||||
)
|
||||
session.close()
|
||||
|
||||
const [emotionsCount] = transactionRes.records.map(record => {
|
||||
return record.get('emotionsCount').low
|
||||
})
|
||||
|
||||
return emotionsCount
|
||||
},
|
||||
PostsEmotionsByCurrentUser: async (object, params, context, resolveInfo) => {
|
||||
const session = context.driver.session()
|
||||
const { postId } = params
|
||||
const transactionRes = await session.run(
|
||||
`MATCH (user:User {id: $userId})-[emoted:EMOTED]->(post:Post {id: $postId})
|
||||
RETURN collect(emoted.emotion) as emotion`,
|
||||
{ userId: context.user.id, postId },
|
||||
)
|
||||
|
||||
session.close()
|
||||
|
||||
const [emotions] = transactionRes.records.map(record => {
|
||||
return record.get('emotion')
|
||||
})
|
||||
return emotions
|
||||
},
|
||||
},
|
||||
Mutation: {
|
||||
UpdatePost: async (object, params, context, resolveInfo) => {
|
||||
const { categoryIds } = params
|
||||
@ -112,39 +179,4 @@ export default {
|
||||
return emoted
|
||||
},
|
||||
},
|
||||
Query: {
|
||||
PostsEmotionsCountByEmotion: async (object, params, context, resolveInfo) => {
|
||||
const session = context.driver.session()
|
||||
const { postId, data } = params
|
||||
const transactionRes = await session.run(
|
||||
`MATCH (post:Post {id: $postId})<-[emoted:EMOTED {emotion: $data.emotion}]-()
|
||||
RETURN COUNT(DISTINCT emoted) as emotionsCount
|
||||
`,
|
||||
{ postId, data },
|
||||
)
|
||||
session.close()
|
||||
|
||||
const [emotionsCount] = transactionRes.records.map(record => {
|
||||
return record.get('emotionsCount').low
|
||||
})
|
||||
|
||||
return emotionsCount
|
||||
},
|
||||
PostsEmotionsByCurrentUser: async (object, params, context, resolveInfo) => {
|
||||
const session = context.driver.session()
|
||||
const { postId } = params
|
||||
const transactionRes = await session.run(
|
||||
`MATCH (user:User {id: $userId})-[emoted:EMOTED]->(post:Post {id: $postId})
|
||||
RETURN collect(emoted.emotion) as emotion`,
|
||||
{ userId: context.user.id, postId },
|
||||
)
|
||||
|
||||
session.close()
|
||||
|
||||
const [emotions] = transactionRes.records.map(record => {
|
||||
return record.get('emotion')
|
||||
})
|
||||
return emotions
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -460,6 +460,7 @@ describe('emotions', () => {
|
||||
context: () => {
|
||||
return {
|
||||
user,
|
||||
neode: instance,
|
||||
driver,
|
||||
}
|
||||
},
|
||||
@ -476,6 +477,7 @@ describe('emotions', () => {
|
||||
context: () => {
|
||||
return {
|
||||
user,
|
||||
neode: instance,
|
||||
driver,
|
||||
}
|
||||
},
|
||||
|
||||
@ -6,8 +6,45 @@ import Resolver from './helpers/Resolver'
|
||||
|
||||
const instance = neode()
|
||||
|
||||
export const getBlockedUsers = async context => {
|
||||
const { neode } = context
|
||||
const userModel = neode.model('User')
|
||||
let blockedUsers = neode
|
||||
.query()
|
||||
.match('user', userModel)
|
||||
.where('user.id', context.user.id)
|
||||
.relationship(userModel.relationships().get('blocked'))
|
||||
.to('blocked', userModel)
|
||||
.return('blocked')
|
||||
blockedUsers = await blockedUsers.execute()
|
||||
blockedUsers = blockedUsers.records.map(r => r.get('blocked').properties)
|
||||
return blockedUsers
|
||||
}
|
||||
|
||||
export const getBlockedByUsers = async context => {
|
||||
const { neode } = context
|
||||
const userModel = neode.model('User')
|
||||
let blockedByUsers = neode
|
||||
.query()
|
||||
.match('user', userModel)
|
||||
.relationship(userModel.relationships().get('blocked'))
|
||||
.to('blocked', userModel)
|
||||
.where('blocked.id', context.user.id)
|
||||
.return('user')
|
||||
blockedByUsers = await blockedByUsers.execute()
|
||||
blockedByUsers = blockedByUsers.records.map(r => r.get('user').properties)
|
||||
return blockedByUsers
|
||||
}
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
blockedUsers: async (object, args, context, resolveInfo) => {
|
||||
try {
|
||||
return getBlockedUsers(context)
|
||||
} catch (e) {
|
||||
throw new UserInputError(e.message)
|
||||
}
|
||||
},
|
||||
User: async (object, args, context, resolveInfo) => {
|
||||
const { email } = args
|
||||
if (email) {
|
||||
@ -20,6 +57,36 @@ export default {
|
||||
},
|
||||
},
|
||||
Mutation: {
|
||||
block: async (object, args, context, resolveInfo) => {
|
||||
const { user: currentUser } = context
|
||||
if (currentUser.id === args.id) return null
|
||||
await instance.cypher(
|
||||
`
|
||||
MATCH(u:User {id: $currentUser.id})-[r:FOLLOWS]->(b:User {id: $args.id})
|
||||
DELETE r
|
||||
`,
|
||||
{ currentUser, args },
|
||||
)
|
||||
const [user, blockedUser] = await Promise.all([
|
||||
instance.find('User', currentUser.id),
|
||||
instance.find('User', args.id),
|
||||
])
|
||||
await user.relateTo(blockedUser, 'blocked')
|
||||
return blockedUser.toJson()
|
||||
},
|
||||
unblock: async (object, args, context, resolveInfo) => {
|
||||
const { user: currentUser } = context
|
||||
if (currentUser.id === args.id) return null
|
||||
await instance.cypher(
|
||||
`
|
||||
MATCH(u:User {id: $currentUser.id})-[r:BLOCKED]->(b:User {id: $args.id})
|
||||
DELETE r
|
||||
`,
|
||||
{ currentUser, args },
|
||||
)
|
||||
const blockedUser = await instance.find('User', args.id)
|
||||
return blockedUser.toJson()
|
||||
},
|
||||
UpdateUser: async (object, args, context, resolveInfo) => {
|
||||
args = await fileUpload(args, { file: 'avatarUpload', url: 'avatar' })
|
||||
try {
|
||||
@ -73,6 +140,12 @@ export default {
|
||||
'locationName',
|
||||
'about',
|
||||
],
|
||||
boolean: {
|
||||
followedByCurrentUser:
|
||||
'MATCH (this)<-[:FOLLOWS]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
||||
isBlocked:
|
||||
'MATCH (this)<-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
||||
},
|
||||
count: {
|
||||
contributionsCount: '-[:WROTE]->(related:Post)',
|
||||
friendsCount: '<-[:FRIENDS]->(related:User)',
|
||||
@ -91,7 +164,6 @@ export default {
|
||||
followedBy: '<-[:FOLLOWS]-(related:User)',
|
||||
following: '-[:FOLLOWS]->(related:User)',
|
||||
friends: '-[:FRIENDS]-(related:User)',
|
||||
blacklisted: '-[:BLACKLISTED]->(related:User)',
|
||||
socialMedia: '-[:OWNED_BY]->(related:SocialMedia',
|
||||
contributions: '-[:WROTE]->(related:Post)',
|
||||
comments: '-[:WROTE]->(related:Comment)',
|
||||
|
||||
393
backend/src/schema/resolvers/users/blockedUsers.spec.js
Normal file
393
backend/src/schema/resolvers/users/blockedUsers.spec.js
Normal file
@ -0,0 +1,393 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../../../server'
|
||||
import Factory from '../../../seed/factories'
|
||||
import { gql } from '../../../jest/helpers'
|
||||
import { neode, getDriver } from '../../../bootstrap/neo4j'
|
||||
|
||||
const driver = getDriver()
|
||||
const factory = Factory()
|
||||
const instance = neode()
|
||||
|
||||
let currentUser
|
||||
let blockedUser
|
||||
let authenticatedUser
|
||||
let server
|
||||
|
||||
beforeEach(() => {
|
||||
authenticatedUser = undefined
|
||||
;({ server } = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
user: authenticatedUser,
|
||||
driver,
|
||||
neode: instance,
|
||||
cypherParams: {
|
||||
currentUserId: authenticatedUser ? authenticatedUser.id : null,
|
||||
},
|
||||
}
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await factory.cleanDatabase()
|
||||
})
|
||||
|
||||
describe('blockedUsers', () => {
|
||||
let blockedUserQuery
|
||||
beforeEach(() => {
|
||||
blockedUserQuery = gql`
|
||||
query {
|
||||
blockedUsers {
|
||||
id
|
||||
name
|
||||
isBlocked
|
||||
}
|
||||
}
|
||||
`
|
||||
})
|
||||
|
||||
it('throws permission error', async () => {
|
||||
const { query } = createTestClient(server)
|
||||
const result = await query({ query: blockedUserQuery })
|
||||
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!')
|
||||
})
|
||||
|
||||
describe('authenticated and given a blocked user', () => {
|
||||
beforeEach(async () => {
|
||||
currentUser = await instance.create('User', {
|
||||
name: 'Current User',
|
||||
id: 'u1',
|
||||
})
|
||||
blockedUser = await instance.create('User', {
|
||||
name: 'Blocked User',
|
||||
id: 'u2',
|
||||
})
|
||||
await currentUser.relateTo(blockedUser, 'blocked')
|
||||
authenticatedUser = await currentUser.toJson()
|
||||
})
|
||||
|
||||
it('returns a list of blocked users', async () => {
|
||||
const { query } = createTestClient(server)
|
||||
await expect(query({ query: blockedUserQuery })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
blockedUsers: [
|
||||
{
|
||||
name: 'Blocked User',
|
||||
id: 'u2',
|
||||
isBlocked: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('block', () => {
|
||||
let blockAction
|
||||
|
||||
beforeEach(() => {
|
||||
currentUser = undefined
|
||||
blockAction = variables => {
|
||||
const { mutate } = createTestClient(server)
|
||||
const blockMutation = gql`
|
||||
mutation($id: ID!) {
|
||||
block(id: $id) {
|
||||
id
|
||||
name
|
||||
isBlocked
|
||||
}
|
||||
}
|
||||
`
|
||||
return mutate({ mutation: blockMutation, variables })
|
||||
}
|
||||
})
|
||||
|
||||
it('throws permission error', async () => {
|
||||
const result = await blockAction({ id: 'u2' })
|
||||
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!')
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
currentUser = await instance.create('User', {
|
||||
name: 'Current User',
|
||||
id: 'u1',
|
||||
})
|
||||
authenticatedUser = await currentUser.toJson()
|
||||
})
|
||||
|
||||
describe('block yourself', () => {
|
||||
it('returns null', async () => {
|
||||
await expect(blockAction({ id: 'u1' })).resolves.toEqual(
|
||||
expect.objectContaining({ data: { block: null } }),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('block not existing user', () => {
|
||||
it('returns null', async () => {
|
||||
await expect(blockAction({ id: 'u2' })).resolves.toEqual(
|
||||
expect.objectContaining({ data: { block: null } }),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('given a to-be-blocked user', () => {
|
||||
beforeEach(async () => {
|
||||
blockedUser = await instance.create('User', {
|
||||
name: 'Blocked User',
|
||||
id: 'u2',
|
||||
})
|
||||
})
|
||||
|
||||
it('blocks a user', async () => {
|
||||
await expect(blockAction({ id: 'u2' })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: { block: { id: 'u2', name: 'Blocked User', isBlocked: true } },
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('unfollows the user', async () => {
|
||||
await currentUser.relateTo(blockedUser, 'following')
|
||||
const queryUser = gql`
|
||||
query {
|
||||
User(id: "u2") {
|
||||
id
|
||||
isBlocked
|
||||
followedByCurrentUser
|
||||
}
|
||||
}
|
||||
`
|
||||
const { query } = createTestClient(server)
|
||||
await expect(query({ query: queryUser })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: { User: [{ id: 'u2', isBlocked: false, followedByCurrentUser: true }] },
|
||||
}),
|
||||
)
|
||||
await blockAction({ id: 'u2' })
|
||||
await expect(query({ query: queryUser })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: { User: [{ id: 'u2', isBlocked: true, followedByCurrentUser: false }] },
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('given both the current user and the to-be-blocked user write a post', () => {
|
||||
let postQuery
|
||||
|
||||
beforeEach(async () => {
|
||||
const post1 = await instance.create('Post', {
|
||||
id: 'p12',
|
||||
title: 'A post written by the current user',
|
||||
})
|
||||
const post2 = await instance.create('Post', {
|
||||
id: 'p23',
|
||||
title: 'A post written by the blocked user',
|
||||
})
|
||||
await Promise.all([
|
||||
post1.relateTo(currentUser, 'author'),
|
||||
post2.relateTo(blockedUser, 'author'),
|
||||
])
|
||||
postQuery = gql`
|
||||
query {
|
||||
Post(orderBy: createdAt_asc) {
|
||||
id
|
||||
title
|
||||
author {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
})
|
||||
|
||||
const bothPostsAreInTheNewsfeed = async () => {
|
||||
const { query } = createTestClient(server)
|
||||
await expect(query({ query: postQuery })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
Post: [
|
||||
{
|
||||
id: 'p12',
|
||||
title: 'A post written by the current user',
|
||||
author: {
|
||||
name: 'Current User',
|
||||
id: 'u1',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'p23',
|
||||
title: 'A post written by the blocked user',
|
||||
author: {
|
||||
name: 'Blocked User',
|
||||
id: 'u2',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
describe('from the perspective of the current user', () => {
|
||||
it('both posts are in the newsfeed', bothPostsAreInTheNewsfeed)
|
||||
|
||||
describe('but if the current user blocks the other user', () => {
|
||||
beforeEach(async () => {
|
||||
await currentUser.relateTo(blockedUser, 'blocked')
|
||||
})
|
||||
|
||||
it("the blocked user's post won't show up in the newsfeed of the current user", async () => {
|
||||
const { query } = createTestClient(server)
|
||||
await expect(query({ query: postQuery })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
Post: [
|
||||
{
|
||||
id: 'p12',
|
||||
title: 'A post written by the current user',
|
||||
author: { name: 'Current User', id: 'u1' },
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('from the perspective of the blocked user', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await blockedUser.toJson()
|
||||
})
|
||||
|
||||
it('both posts are in the newsfeed', bothPostsAreInTheNewsfeed)
|
||||
describe('but if the current user blocks the other user', () => {
|
||||
beforeEach(async () => {
|
||||
await currentUser.relateTo(blockedUser, 'blocked')
|
||||
})
|
||||
|
||||
it("the current user's post won't show up in the newsfeed of the blocked user", async () => {
|
||||
const { query } = createTestClient(server)
|
||||
await expect(query({ query: postQuery })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
Post: [
|
||||
{
|
||||
id: 'p23',
|
||||
title: 'A post written by the blocked user',
|
||||
author: { name: 'Blocked User', id: 'u2' },
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('unblock', () => {
|
||||
let unblockAction
|
||||
|
||||
beforeEach(() => {
|
||||
currentUser = undefined
|
||||
unblockAction = variables => {
|
||||
const { mutate } = createTestClient(server)
|
||||
const unblockMutation = gql`
|
||||
mutation($id: ID!) {
|
||||
unblock(id: $id) {
|
||||
id
|
||||
name
|
||||
isBlocked
|
||||
}
|
||||
}
|
||||
`
|
||||
return mutate({ mutation: unblockMutation, variables })
|
||||
}
|
||||
})
|
||||
|
||||
it('throws permission error', async () => {
|
||||
const result = await unblockAction({ id: 'u2' })
|
||||
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!')
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
currentUser = await instance.create('User', {
|
||||
name: 'Current User',
|
||||
id: 'u1',
|
||||
})
|
||||
authenticatedUser = await currentUser.toJson()
|
||||
})
|
||||
|
||||
describe('unblock yourself', () => {
|
||||
it('returns null', async () => {
|
||||
await expect(unblockAction({ id: 'u1' })).resolves.toEqual(
|
||||
expect.objectContaining({ data: { unblock: null } }),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('unblock not-existing user', () => {
|
||||
it('returns null', async () => {
|
||||
await expect(unblockAction({ id: 'lksjdflksfdj' })).resolves.toEqual(
|
||||
expect.objectContaining({ data: { unblock: null } }),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('given another user', () => {
|
||||
beforeEach(async () => {
|
||||
blockedUser = await instance.create('User', {
|
||||
name: 'Blocked User',
|
||||
id: 'u2',
|
||||
})
|
||||
})
|
||||
|
||||
describe('unblocking a not yet blocked user', () => {
|
||||
it('does not hurt', async () => {
|
||||
await expect(unblockAction({ id: 'u2' })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: { unblock: { id: 'u2', name: 'Blocked User', isBlocked: false } },
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('given a blocked user', () => {
|
||||
beforeEach(async () => {
|
||||
await currentUser.relateTo(blockedUser, 'blocked')
|
||||
})
|
||||
|
||||
it('unblocks a user', async () => {
|
||||
await expect(unblockAction({ id: 'u2' })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: { unblock: { id: 'u2', name: 'Blocked User', isBlocked: false } },
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('unblocking twice', () => {
|
||||
it('has no effect', async () => {
|
||||
await unblockAction({ id: 'u2' })
|
||||
await expect(unblockAction({ id: 'u2' })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: { unblock: { id: 'u2', name: 'Blocked User', isBlocked: false } },
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -4,15 +4,16 @@ type Query {
|
||||
currentUser: User
|
||||
# Get the latest Network Statistics
|
||||
statistics: Statistics!
|
||||
findPosts(filter: String!, limit: Int = 10): [Post]!
|
||||
findPosts(query: String!, limit: Int = 10): [Post]!
|
||||
@cypher(
|
||||
statement: """
|
||||
CALL db.index.fulltext.queryNodes('full_text_search', $filter)
|
||||
CALL db.index.fulltext.queryNodes('full_text_search', $query)
|
||||
YIELD node as post, score
|
||||
MATCH (post)<-[:WROTE]-(user:User)
|
||||
WHERE score >= 0.2
|
||||
AND NOT user.deleted = true AND NOT user.disabled = true
|
||||
AND NOT post.deleted = true AND NOT post.disabled = true
|
||||
AND NOT user.id in COALESCE($filter.author_not.id_in, [])
|
||||
RETURN post
|
||||
LIMIT $limit
|
||||
"""
|
||||
@ -50,14 +51,6 @@ type Statistics {
|
||||
countShouts: Int!
|
||||
}
|
||||
|
||||
type Notification {
|
||||
id: ID!
|
||||
read: Boolean
|
||||
user: User @relation(name: "NOTIFIED", direction: "OUT")
|
||||
post: Post @relation(name: "NOTIFIED", direction: "IN")
|
||||
createdAt: String
|
||||
}
|
||||
|
||||
type Location {
|
||||
id: ID!
|
||||
name: String!
|
||||
|
||||
7
backend/src/schema/types/type/Notification.gql
Normal file
7
backend/src/schema/types/type/Notification.gql
Normal file
@ -0,0 +1,7 @@
|
||||
type Notification {
|
||||
id: ID!
|
||||
read: Boolean
|
||||
user: User @relation(name: "NOTIFIED", direction: "OUT")
|
||||
post: Post @relation(name: "NOTIFIED", direction: "IN")
|
||||
createdAt: String
|
||||
}
|
||||
@ -42,6 +42,12 @@ type User {
|
||||
RETURN COUNT(u) >= 1
|
||||
"""
|
||||
)
|
||||
isBlocked: Boolean! @cypher(
|
||||
statement: """
|
||||
MATCH (this)<-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId})
|
||||
RETURN COUNT(u) >= 1
|
||||
"""
|
||||
)
|
||||
|
||||
#contributions: [WrittenPost]!
|
||||
#contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]!
|
||||
@ -67,8 +73,6 @@ type User {
|
||||
organizationsCreated: [Organization] @relation(name: "CREATED_ORGA", direction: "OUT")
|
||||
organizationsOwned: [Organization] @relation(name: "OWNING_ORGA", direction: "OUT")
|
||||
|
||||
blacklisted: [User]! @relation(name: "BLACKLISTED", direction: "OUT")
|
||||
|
||||
categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT")
|
||||
|
||||
badges: [Badge]! @relation(name: "REWARDED", direction: "IN")
|
||||
@ -148,6 +152,8 @@ type Query {
|
||||
orderBy: [_UserOrdering]
|
||||
filter: _UserFilter
|
||||
): [User]
|
||||
|
||||
blockedUsers: [User]
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
@ -164,4 +170,8 @@ type Mutation {
|
||||
): User
|
||||
|
||||
DeleteUser(id: ID!, resource: [Deletable]): User
|
||||
|
||||
|
||||
block(id: ID!): User
|
||||
unblock(id: ID!): User
|
||||
}
|
||||
|
||||
@ -120,60 +120,22 @@ import Factory from './factories'
|
||||
bobDerBaumeister.relateTo(turtle, 'rewarded'),
|
||||
jennyRostock.relateTo(bear, 'rewarded'),
|
||||
dagobert.relateTo(rabbit, 'rewarded'),
|
||||
])
|
||||
|
||||
await Promise.all([
|
||||
f.relate('User', 'Friends', {
|
||||
from: 'u1',
|
||||
to: 'u2',
|
||||
}),
|
||||
f.relate('User', 'Friends', {
|
||||
from: 'u1',
|
||||
to: 'u3',
|
||||
}),
|
||||
f.relate('User', 'Friends', {
|
||||
from: 'u2',
|
||||
to: 'u3',
|
||||
}),
|
||||
f.relate('User', 'Blacklisted', {
|
||||
from: 'u7',
|
||||
to: 'u4',
|
||||
}),
|
||||
f.relate('User', 'Blacklisted', {
|
||||
from: 'u7',
|
||||
to: 'u5',
|
||||
}),
|
||||
f.relate('User', 'Blacklisted', {
|
||||
from: 'u7',
|
||||
to: 'u6',
|
||||
}),
|
||||
])
|
||||
peterLustig.relateTo(bobDerBaumeister, 'friends'),
|
||||
peterLustig.relateTo(jennyRostock, 'friends'),
|
||||
bobDerBaumeister.relateTo(jennyRostock, 'friends'),
|
||||
|
||||
await Promise.all([
|
||||
asAdmin.follow({
|
||||
id: 'u3',
|
||||
type: 'User',
|
||||
}),
|
||||
asModerator.follow({
|
||||
id: 'u4',
|
||||
type: 'User',
|
||||
}),
|
||||
asUser.follow({
|
||||
id: 'u4',
|
||||
type: 'User',
|
||||
}),
|
||||
asTick.follow({
|
||||
id: 'u6',
|
||||
type: 'User',
|
||||
}),
|
||||
asTrick.follow({
|
||||
id: 'u4',
|
||||
type: 'User',
|
||||
}),
|
||||
asTrack.follow({
|
||||
id: 'u3',
|
||||
type: 'User',
|
||||
}),
|
||||
peterLustig.relateTo(jennyRostock, 'following'),
|
||||
peterLustig.relateTo(tick, 'following'),
|
||||
bobDerBaumeister.relateTo(tick, 'following'),
|
||||
jennyRostock.relateTo(tick, 'following'),
|
||||
tick.relateTo(track, 'following'),
|
||||
trick.relateTo(tick, 'following'),
|
||||
track.relateTo(jennyRostock, 'following'),
|
||||
|
||||
dagobert.relateTo(tick, 'blocked'),
|
||||
dagobert.relateTo(trick, 'blocked'),
|
||||
dagobert.relateTo(track, 'blocked'),
|
||||
])
|
||||
|
||||
await Promise.all([
|
||||
|
||||
@ -3,7 +3,7 @@ import helmet from 'helmet'
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import CONFIG, { requiredConfigs } from './config'
|
||||
import middleware from './middleware'
|
||||
import { getDriver } from './bootstrap/neo4j'
|
||||
import { neode as getNeode, getDriver } from './bootstrap/neo4j'
|
||||
import decode from './jwt/decode'
|
||||
import schema from './schema'
|
||||
|
||||
@ -16,6 +16,7 @@ Object.entries(requiredConfigs).map(entry => {
|
||||
})
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
|
||||
const createServer = options => {
|
||||
const defaults = {
|
||||
@ -23,6 +24,7 @@ const createServer = options => {
|
||||
const user = await decode(driver, req.headers.authorization)
|
||||
return {
|
||||
driver,
|
||||
neode,
|
||||
user,
|
||||
req,
|
||||
cypherParams: {
|
||||
|
||||
@ -11,6 +11,13 @@ Then("I should have one post in the select dropdown", () => {
|
||||
});
|
||||
});
|
||||
|
||||
Then("the search has no results", () => {
|
||||
cy.get(".input .ds-select-dropdown").should($li => {
|
||||
expect($li).to.have.length(1);
|
||||
});
|
||||
cy.get(".ds-select-dropdown").should("contain", 'Nothing found');
|
||||
});
|
||||
|
||||
Then("I should see the following posts in the select dropdown:", table => {
|
||||
table.hashes().forEach(({ title }) => {
|
||||
cy.get(".ds-select-dropdown").should("contain", title);
|
||||
|
||||
@ -362,3 +362,96 @@ Then("the notification gets marked as read", () => {
|
||||
Then("there are no notifications in the top menu", () => {
|
||||
cy.get(".notifications-menu").should("contain", "0");
|
||||
});
|
||||
|
||||
Given("there is an annoying user called {string}", (name) => {
|
||||
const annoyingParams = {
|
||||
email: 'spammy-spammer@example.org',
|
||||
password: '1234',
|
||||
}
|
||||
cy.factory().create('User', {
|
||||
...annoyingParams,
|
||||
id: 'annoying-user',
|
||||
name
|
||||
})
|
||||
})
|
||||
|
||||
Given("I am on the profile page of the annoying user", (name) => {
|
||||
cy.openPage('/profile/annoying-user/spammy-spammer');
|
||||
})
|
||||
|
||||
When("I visit the profile page of the annoying user", (name) => {
|
||||
cy.openPage('/profile/annoying-user');
|
||||
})
|
||||
|
||||
When("I ", (name) => {
|
||||
cy.openPage('/profile/annoying-user');
|
||||
})
|
||||
|
||||
When("I click on {string} from the content menu in the user info box", (button) => {
|
||||
cy.get('.user-content-menu .content-menu-trigger')
|
||||
.click()
|
||||
cy.get('.popover .ds-menu-item-link')
|
||||
.contains(button)
|
||||
.click()
|
||||
})
|
||||
|
||||
When ("I navigate to my {string} settings page", (settingsPage) => {
|
||||
cy.get(".avatar-menu").click();
|
||||
cy.get(".avatar-menu-popover")
|
||||
.find('a[href]').contains("Settings").click()
|
||||
cy.contains('.ds-menu-item-link', settingsPage).click()
|
||||
})
|
||||
|
||||
Given("I follow the user {string}", (name) => {
|
||||
cy.neode()
|
||||
.first('User', { name }).then((followed) => {
|
||||
cy.neode()
|
||||
.first('User', {name: narratorParams.name})
|
||||
.relateTo(followed, 'following')
|
||||
})
|
||||
})
|
||||
|
||||
Given("\"Spammy Spammer\" wrote a post {string}", (title) => {
|
||||
cy.factory()
|
||||
.authenticateAs({
|
||||
email: 'spammy-spammer@example.org',
|
||||
password: '1234',
|
||||
})
|
||||
.create("Post", { title })
|
||||
})
|
||||
|
||||
Then("the list of posts of this user is empty", () => {
|
||||
cy.get('.ds-card-content').not('.post-link')
|
||||
cy.get('.main-container').find('.ds-space.hc-empty')
|
||||
})
|
||||
|
||||
Then("nobody is following the user profile anymore", () => {
|
||||
cy.get('.ds-card-content').not('.post-link')
|
||||
cy.get('.main-container').contains('.ds-card-content', 'is not followed by anyone')
|
||||
})
|
||||
|
||||
Given("I wrote a post {string}", (title) => {
|
||||
cy.factory()
|
||||
.authenticateAs(loginCredentials)
|
||||
.create("Post", { title })
|
||||
})
|
||||
|
||||
When("I block the user {string}", (name) => {
|
||||
cy.neode()
|
||||
.first('User', { name }).then((blocked) => {
|
||||
cy.neode()
|
||||
.first('User', {name: narratorParams.name})
|
||||
.relateTo(blocked, 'blocked')
|
||||
})
|
||||
})
|
||||
|
||||
When("I log in with:", (table) => {
|
||||
const [firstRow] = table.hashes()
|
||||
const { Email, Password } = firstRow
|
||||
cy.login({email: Email, password: Password})
|
||||
})
|
||||
|
||||
Then("I see only one post with the title {string}", (title) => {
|
||||
cy.get('.main-container').find('.post-link').should('have.length', 1)
|
||||
cy.get('.main-container').contains('.post-link', title)
|
||||
})
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
Feature: Block a User
|
||||
As a user
|
||||
I'd like to have a button to block another user
|
||||
To prevent him from seeing and interacting with my contributions and also to avoid seeing his/her posts
|
||||
|
||||
Background:
|
||||
Given I have a user account
|
||||
And there is an annoying user called "Spammy Spammer"
|
||||
And I am logged in
|
||||
|
||||
Scenario: Block a user
|
||||
Given I am on the profile page of the annoying user
|
||||
When I click on "Block user" from the content menu in the user info box
|
||||
And I navigate to my "Blocked users" settings page
|
||||
Then I can see the following table:
|
||||
| Avatar | Name |
|
||||
| | Spammy Spammer |
|
||||
|
||||
Scenario: Block a previously followed user
|
||||
Given I follow the user "Spammy Spammer"
|
||||
And "Spammy Spammer" wrote a post "Spam Spam Spam"
|
||||
When I visit the profile page of the annoying user
|
||||
And I click on "Block user" from the content menu in the user info box
|
||||
Then the list of posts of this user is empty
|
||||
And nobody is following the user profile anymore
|
||||
|
||||
Scenario: Posts of blocked users are filtered from search results
|
||||
Given "Spammy Spammer" wrote a post "Spam Spam Spam"
|
||||
When I search for "Spam"
|
||||
Then I should see the following posts in the select dropdown:
|
||||
| title |
|
||||
| Spam Spam Spam |
|
||||
When I block the user "Spammy Spammer"
|
||||
And I refresh the page
|
||||
And I search for "Spam"
|
||||
Then the search has no results
|
||||
@ -0,0 +1,22 @@
|
||||
Feature: Block a User
|
||||
As a user
|
||||
I'd like to have a button to block another user
|
||||
To prevent him from seeing and interacting with my contributions and also to avoid seeing his/her posts
|
||||
|
||||
Background:
|
||||
Given I have a user account
|
||||
And there is an annoying user called "Spammy Spammer"
|
||||
|
||||
Scenario Outline: Blocked users cannot see each others posts
|
||||
Given "Spammy Spammer" wrote a post "Spam Spam Spam"
|
||||
And I wrote a post "I hate spammers"
|
||||
And I block the user "Spammy Spammer"
|
||||
When I log in with:
|
||||
| Email | Password |
|
||||
| <email> | <password> |
|
||||
Then I see only one post with the title "<expected_title>"
|
||||
Examples:
|
||||
| email | password | expected_title |
|
||||
| peterpan@example.org | 1234 | I hate spammers |
|
||||
| spammy-spammer@example.org | 1234 | Spam Spam Spam |
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import Factory from '../../backend/src/seed/factories'
|
||||
import { getDriver } from '../../backend/src/bootstrap/neo4j'
|
||||
import { getDriver, neode as getNeode } from '../../backend/src/bootstrap/neo4j'
|
||||
import setupNeode from '../../backend/src/bootstrap/neode'
|
||||
import neode from 'neode'
|
||||
|
||||
@ -16,6 +16,24 @@ beforeEach(async () => {
|
||||
await factory.cleanDatabase({ seedServerHost, neo4jDriver })
|
||||
})
|
||||
|
||||
Cypress.Commands.add('neode', () => {
|
||||
return setupNeode(neo4jConfigs)
|
||||
})
|
||||
Cypress.Commands.add(
|
||||
'first',
|
||||
{ prevSubject: true },
|
||||
async (neode, model, properties) => {
|
||||
return neode.first(model, properties)
|
||||
}
|
||||
)
|
||||
Cypress.Commands.add(
|
||||
'relateTo',
|
||||
{ prevSubject: true },
|
||||
async (node, otherNode, relationship) => {
|
||||
return node.relateTo(otherNode, relationship)
|
||||
}
|
||||
)
|
||||
|
||||
Cypress.Commands.add('factory', () => {
|
||||
return Factory({ seedServerHost, neo4jDriver, neodeInstance: setupNeode(neo4jConfigs) })
|
||||
})
|
||||
|
||||
@ -122,13 +122,34 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isOwner && this.resourceType === 'user') {
|
||||
routes.push({
|
||||
name: this.$t(`settings.name`),
|
||||
path: '/settings',
|
||||
icon: 'edit',
|
||||
})
|
||||
if (this.resourceType === 'user') {
|
||||
if (this.isOwner) {
|
||||
routes.push({
|
||||
name: this.$t(`settings.name`),
|
||||
path: '/settings',
|
||||
icon: 'edit',
|
||||
})
|
||||
} else {
|
||||
if (this.resource.isBlocked) {
|
||||
routes.push({
|
||||
name: this.$t(`settings.blocked-users.unblock`),
|
||||
callback: () => {
|
||||
this.$emit('unblock', this.resource)
|
||||
},
|
||||
icon: 'user-plus',
|
||||
})
|
||||
} else {
|
||||
routes.push({
|
||||
name: this.$t(`settings.blocked-users.block`),
|
||||
callback: () => {
|
||||
this.$emit('block', this.resource)
|
||||
},
|
||||
icon: 'user-times',
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return routes
|
||||
},
|
||||
isModerator() {
|
||||
|
||||
@ -46,6 +46,7 @@ export default i18n => {
|
||||
}
|
||||
followedByCount
|
||||
followedByCurrentUser
|
||||
isBlocked
|
||||
followedBy(first: 7) {
|
||||
id
|
||||
slug
|
||||
|
||||
39
webapp/graphql/settings/BlockedUsers.js
Normal file
39
webapp/graphql/settings/BlockedUsers.js
Normal file
@ -0,0 +1,39 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const BlockedUsers = () => {
|
||||
return gql(`
|
||||
{
|
||||
blockedUsers {
|
||||
id
|
||||
name
|
||||
slug
|
||||
avatar
|
||||
about
|
||||
disabled
|
||||
deleted
|
||||
}
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
export const Block = () => {
|
||||
return gql(`mutation($id:ID!) {
|
||||
block(id: $id) {
|
||||
id
|
||||
name
|
||||
isBlocked
|
||||
followedByCurrentUser
|
||||
}
|
||||
}`)
|
||||
}
|
||||
|
||||
export const Unblock = () => {
|
||||
return gql(`mutation($id:ID!) {
|
||||
unblock(id: $id) {
|
||||
id
|
||||
name
|
||||
isBlocked
|
||||
followedByCurrentUser
|
||||
}
|
||||
}`)
|
||||
}
|
||||
@ -189,6 +189,25 @@
|
||||
"submit": "Link hinzufügen",
|
||||
"successAdd": "Social-Media hinzugefügt. Profil aktualisiert!",
|
||||
"successDelete": "Social-Media gelöscht. Profil aktualisiert!"
|
||||
},
|
||||
"blocked-users": {
|
||||
"name": "Blockierte Benutzer",
|
||||
"explanation": {
|
||||
"intro": "Wenn ein anderer Benutzer von dir blockiert wurde, dann passiert folgendes:",
|
||||
"your-perspective": "In deiner Beitragsübersicht tauchen keine Beiträge der blockierten Person mehr auf.",
|
||||
"their-perspective": "Umgekehrt das gleiche: Die blockierte Person sieht deine Beiträge auch nicht mehr in ihrer Übersicht.",
|
||||
"search": "Die Beiträge von blockierten Personen verschwinden aus deinen Suchergebnissen.",
|
||||
"notifications": "Von dir blockierte Personen erhalten keine Benachrichtigungen mehr, wenn sie in deinen Beiträgen erwähnt werden.",
|
||||
"closing": "Das sollte fürs Erste genügen, damit blockierte Benutzer dich nicht mehr länger belästigen können."
|
||||
},
|
||||
"columns": {
|
||||
"name": "Name",
|
||||
"slug": "Alias"
|
||||
},
|
||||
"empty": "Bislang hast du niemanden blockiert.",
|
||||
"how-to": "Du kannst andere Benutzer auf deren Profilseite über das Inhaltsmenü blockieren.",
|
||||
"block": "Nutzer blockieren",
|
||||
"unblock": "Nutzer entblocken"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
|
||||
@ -189,6 +189,25 @@
|
||||
"submit": "Add link",
|
||||
"successAdd": "Added social media. Updated user profile!",
|
||||
"successDelete": "Deleted social media. Updated user profile!"
|
||||
},
|
||||
"blocked-users": {
|
||||
"name": "Blocked users",
|
||||
"explanation": {
|
||||
"intro": "If another user has been blocked by you, this is what happens:",
|
||||
"your-perspective": "The blocked person's posts will no longer appear in your news feed.",
|
||||
"their-perspective": "Vice versa: The blocked person will also no longer see your posts in their news feed.",
|
||||
"search": "Posts of blocked people disappear from your search results.",
|
||||
"notifications": "Blocked users will no longer receive notifications if they are mentioned in your posts.",
|
||||
"closing": "This should be sufficient for now so that blocked users can no longer bother you."
|
||||
},
|
||||
"columns": {
|
||||
"name": "Name",
|
||||
"slug": "Slug"
|
||||
},
|
||||
"empty": "So far, you have not blocked anybody.",
|
||||
"how-to": "You can block other users on their profile page via the content menu.",
|
||||
"block": "Block user",
|
||||
"unblock": "Unblock user"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
|
||||
@ -22,6 +22,8 @@
|
||||
:resource="user"
|
||||
:is-owner="myProfile"
|
||||
class="user-content-menu"
|
||||
@block="block"
|
||||
@unblock="unblock"
|
||||
/>
|
||||
</no-ssr>
|
||||
<ds-space margin="small">
|
||||
@ -54,13 +56,18 @@
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
<ds-space margin="small">
|
||||
<hc-follow-button
|
||||
v-if="!myProfile"
|
||||
:follow-id="user.id"
|
||||
:is-followed="user.followedByCurrentUser"
|
||||
@optimistic="follow => (user.followedByCurrentUser = follow)"
|
||||
@update="follow => fetchUser()"
|
||||
/>
|
||||
<template v-if="!myProfile">
|
||||
<hc-follow-button
|
||||
v-if="!user.isBlocked"
|
||||
:follow-id="user.id"
|
||||
:is-followed="user.followedByCurrentUser"
|
||||
@optimistic="follow => (user.followedByCurrentUser = follow)"
|
||||
@update="follow => fetchUser()"
|
||||
/>
|
||||
<ds-button v-else fullwidth @click="unblock(user)">
|
||||
{{ $t('settings.blocked-users.unblock') }}
|
||||
</ds-button>
|
||||
</template>
|
||||
</ds-space>
|
||||
<template v-if="user.about">
|
||||
<hr />
|
||||
@ -242,6 +249,7 @@ import HcUpload from '~/components/Upload'
|
||||
import HcAvatar from '~/components/Avatar/Avatar.vue'
|
||||
import PostQuery from '~/graphql/UserProfile/Post.js'
|
||||
import UserQuery from '~/graphql/UserProfile/User.js'
|
||||
import { Block, Unblock } from '~/graphql/settings/BlockedUsers.js'
|
||||
|
||||
const tabToFilterMapping = ({ tab, id }) => {
|
||||
return {
|
||||
@ -372,6 +380,16 @@ export default {
|
||||
}
|
||||
return this.uniq(this.Post.filter(post => !post.deleted))
|
||||
},
|
||||
async block(user) {
|
||||
await this.$apollo.mutate({ mutation: Block(), variables: { id: user.id } })
|
||||
this.$apollo.queries.User.refetch()
|
||||
this.$apollo.queries.Post.refetch()
|
||||
},
|
||||
async unblock(user) {
|
||||
await this.$apollo.mutate({ mutation: Unblock(), variables: { id: user.id } })
|
||||
this.$apollo.queries.User.refetch()
|
||||
this.$apollo.queries.Post.refetch()
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
Post: {
|
||||
|
||||
@ -31,6 +31,10 @@ export default {
|
||||
name: this.$t('settings.social-media.name'),
|
||||
path: `/settings/my-social-media`,
|
||||
},
|
||||
{
|
||||
name: this.$t('settings.blocked-users.name'),
|
||||
path: `/settings/blocked-users`,
|
||||
},
|
||||
{
|
||||
name: this.$t('settings.deleteUserAccount.name'),
|
||||
path: `/settings/delete-account`,
|
||||
|
||||
108
webapp/pages/settings/blocked-users.vue
Normal file
108
webapp/pages/settings/blocked-users.vue
Normal file
@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-space>
|
||||
<ds-card :header="$t('settings.blocked-users.name')">
|
||||
<ds-text>
|
||||
{{ $t('settings.blocked-users.explanation.intro') }}
|
||||
</ds-text>
|
||||
<ds-list>
|
||||
<ds-list-item>
|
||||
{{ $t('settings.blocked-users.explanation.your-perspective') }}
|
||||
</ds-list-item>
|
||||
<ds-list-item>
|
||||
{{ $t('settings.blocked-users.explanation.their-perspective') }}
|
||||
</ds-list-item>
|
||||
<ds-list-item>
|
||||
{{ $t('settings.blocked-users.explanation.search') }}
|
||||
</ds-list-item>
|
||||
<ds-list-item>
|
||||
{{ $t('settings.blocked-users.explanation.notifications') }}
|
||||
</ds-list-item>
|
||||
</ds-list>
|
||||
<ds-text>
|
||||
{{ $t('settings.blocked-users.explanation.closing') }}
|
||||
</ds-text>
|
||||
</ds-card>
|
||||
</ds-space>
|
||||
<ds-card v-if="blockedUsers && blockedUsers.length">
|
||||
<ds-table :data="blockedUsers" :fields="fields" condensed>
|
||||
<template slot="avatar" slot-scope="scope">
|
||||
<nuxt-link
|
||||
:to="{
|
||||
name: 'profile-id-slug',
|
||||
params: { id: scope.row.id, slug: scope.row.slug },
|
||||
}"
|
||||
>
|
||||
<hc-avatar :user="scope.row" size="small" />
|
||||
</nuxt-link>
|
||||
</template>
|
||||
<template slot="name" slot-scope="scope">
|
||||
<nuxt-link
|
||||
:to="{
|
||||
name: 'profile-id-slug',
|
||||
params: { id: scope.row.id, slug: scope.row.slug },
|
||||
}"
|
||||
>
|
||||
<b>{{ scope.row.name | truncate(20) }}</b>
|
||||
</nuxt-link>
|
||||
</template>
|
||||
<template slot="slug" slot-scope="scope">
|
||||
<nuxt-link
|
||||
:to="{
|
||||
name: 'profile-id-slug',
|
||||
params: { id: scope.row.id, slug: scope.row.slug },
|
||||
}"
|
||||
>
|
||||
<b>{{ scope.row.slug | truncate(20) }}</b>
|
||||
</nuxt-link>
|
||||
</template>
|
||||
</ds-table>
|
||||
</ds-card>
|
||||
<ds-card v-else>
|
||||
<ds-space>
|
||||
<ds-placeholder>
|
||||
{{ $t('settings.blocked-users.empty') }}
|
||||
</ds-placeholder>
|
||||
</ds-space>
|
||||
<ds-space>
|
||||
<ds-text align="center">
|
||||
{{ $t('settings.blocked-users.how-to') }}
|
||||
</ds-text>
|
||||
</ds-space>
|
||||
</ds-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { BlockedUsers } from '~/graphql/settings/BlockedUsers'
|
||||
import HcAvatar from '~/components/Avatar/Avatar.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HcAvatar,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
blockedUsers: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fields() {
|
||||
return {
|
||||
avatar: '',
|
||||
name: this.$t('settings.blocked-users.columns.name'),
|
||||
slug: this.$t('settings.blocked-users.columns.slug'),
|
||||
}
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
blockedUsers: { query: BlockedUsers, fetchPolicy: 'cache-and-network' },
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.ds-table-col {
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
@ -46,8 +46,8 @@ export const actions = {
|
||||
await this.app.apolloProvider.defaultClient
|
||||
.query({
|
||||
query: gql(`
|
||||
query findPosts($filter: String!) {
|
||||
findPosts(filter: $filter, limit: 10) {
|
||||
query findPosts($query: String!) {
|
||||
findPosts(query: $query, limit: 10) {
|
||||
id
|
||||
slug
|
||||
label: title
|
||||
@ -64,7 +64,7 @@ export const actions = {
|
||||
}
|
||||
`),
|
||||
variables: {
|
||||
filter: value.replace(/\s/g, '~ ') + '~',
|
||||
query: value.replace(/\s/g, '~ ') + '~',
|
||||
},
|
||||
})
|
||||
.then(res => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user