mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-12 23:35:58 +00:00
Get categories working with UpdatePost, refactor
This commit is contained in:
parent
b8ea32cfc1
commit
f302961376
@ -88,21 +88,26 @@ describe('currentUser { notifications }', () => {
|
||||
describe('who mentions me again', () => {
|
||||
beforeEach(async () => {
|
||||
const updatedContent = `${post.content} One more mention to <a href="/profile/you" class="mention">@al-capone</a>`
|
||||
const updatedTitle = 'this post has been updated'
|
||||
// The response `post.content` contains a link but the XSSmiddleware
|
||||
// should have the `mention` CSS class removed. I discovered this
|
||||
// during development and thought: A feature not a bug! This way we
|
||||
// can encode a re-mentioning of users when you edit your post or
|
||||
// comment.
|
||||
const createPostMutation = `
|
||||
mutation($id: ID!, $content: String!) {
|
||||
UpdatePost(id: $id, content: $content) {
|
||||
const updatePostMutation = `
|
||||
mutation($id: ID!, $title: String!, $content: String!) {
|
||||
UpdatePost(id: $id, title: $title, content: $content) {
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
authorClient = new GraphQLClient(host, { headers: authorHeaders })
|
||||
await authorClient.request(createPostMutation, { id: post.id, content: updatedContent })
|
||||
await authorClient.request(updatePostMutation, {
|
||||
id: post.id,
|
||||
content: updatedContent,
|
||||
title: updatedTitle,
|
||||
})
|
||||
})
|
||||
|
||||
it('creates exactly one more notification', async () => {
|
||||
|
||||
@ -17,6 +17,10 @@ export default {
|
||||
args.slug = args.slug || (await uniqueSlug(args.title, isUniqueFor(context, 'Post')))
|
||||
return resolve(root, args, context, info)
|
||||
},
|
||||
UpdatePost: async (resolve, root, args, context, info) => {
|
||||
args.slug = args.slug || (await uniqueSlug(args.title, isUniqueFor(context, 'Post')))
|
||||
return resolve(root, args, context, info)
|
||||
},
|
||||
CreateUser: async (resolve, root, args, context, info) => {
|
||||
args.slug = args.slug || (await uniqueSlug(args.name, isUniqueFor(context, 'User')))
|
||||
return resolve(root, args, context, info)
|
||||
|
||||
@ -1,12 +1,43 @@
|
||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
import uuid from 'uuid/v4'
|
||||
import fileUpload from './fileUpload'
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
UpdatePost: async (object, params, context, resolveInfo) => {
|
||||
const { id: postId, categoryIds } = params
|
||||
delete params.categoryIds
|
||||
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
|
||||
return neo4jgraphql(object, params, context, resolveInfo, false)
|
||||
|
||||
const session = context.driver.session()
|
||||
const cypherDeletePreviousRelations = `
|
||||
MATCH (post:Post { id: $postId })-[previousRelations:CATEGORIZED]->(category:Category)
|
||||
DELETE previousRelations
|
||||
RETURN post, category
|
||||
`
|
||||
|
||||
await session.run(cypherDeletePreviousRelations, { postId })
|
||||
|
||||
let updatePostCypher = `MATCH (post:Post {id: $postId})
|
||||
SET post = $params
|
||||
`
|
||||
if (categoryIds) {
|
||||
updatePostCypher += `WITH post
|
||||
UNWIND $categoryIds AS categoryId
|
||||
MATCH (category:Category {id: categoryId})
|
||||
MERGE (post)-[:CATEGORIZED]->(category)
|
||||
`
|
||||
}
|
||||
updatePostCypher += `RETURN post`
|
||||
const updatePostVariables = { postId, categoryIds, params }
|
||||
|
||||
const transactionRes = await session.run(updatePostCypher, updatePostVariables)
|
||||
const [post] = transactionRes.records.map(record => {
|
||||
return record.get('post')
|
||||
})
|
||||
|
||||
session.close()
|
||||
|
||||
return post.properties
|
||||
},
|
||||
|
||||
CreatePost: async (object, params, context, resolveInfo) => {
|
||||
@ -14,23 +45,23 @@ export default {
|
||||
delete params.categoryIds
|
||||
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
|
||||
params.id = params.id || uuid()
|
||||
let cypher = `CREATE (post:Post {params})
|
||||
let createPostCypher = `CREATE (post:Post {params})
|
||||
WITH post
|
||||
MATCH (author:User {id: $userId})
|
||||
MERGE (post)<-[:WROTE]-(author)
|
||||
`
|
||||
if (categoryIds) {
|
||||
cypher += `WITH post
|
||||
createPostCypher += `WITH post
|
||||
UNWIND $categoryIds AS categoryId
|
||||
MATCH (category:Category {id: categoryId})
|
||||
MERGE (post)-[:CATEGORIZED]->(category)
|
||||
`
|
||||
}
|
||||
cypher += `RETURN post`
|
||||
const variables = { userId: context.user.id, categoryIds, params }
|
||||
createPostCypher += `RETURN post`
|
||||
const createPostVariables = { userId: context.user.id, categoryIds, params }
|
||||
|
||||
const session = context.driver.session()
|
||||
const transactionRes = await session.run(cypher, variables)
|
||||
const transactionRes = await session.run(createPostCypher, createPostVariables)
|
||||
|
||||
const [post] = transactionRes.records.map(record => {
|
||||
return record.get('post')
|
||||
|
||||
@ -6,7 +6,32 @@ const factory = Factory()
|
||||
let client
|
||||
const postTitle = 'I am a title'
|
||||
const postContent = 'Some content'
|
||||
const oldTitle = 'Old title'
|
||||
const oldContent = 'Old content'
|
||||
const newTitle = 'New title'
|
||||
const newContent = 'New content'
|
||||
const createPostVariables = { title: postTitle, content: postContent }
|
||||
const createPostWithCategoriesMutation = `
|
||||
mutation($title: String!, $content: String!, $categoryIds: [ID]) {
|
||||
CreatePost(title: $title, content: $content, categoryIds: $categoryIds) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
const creatPostWithCategoriesVariables = {
|
||||
title: postTitle,
|
||||
content: postContent,
|
||||
categoryIds: ['cat9', 'cat4', 'cat15'],
|
||||
}
|
||||
const postQueryWithCategories = `
|
||||
query($id: ID) {
|
||||
Post(id: $id) {
|
||||
categories {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
beforeEach(async () => {
|
||||
await factory.create('User', {
|
||||
email: 'test@example.org',
|
||||
@ -117,38 +142,7 @@ describe('CreatePost', () => {
|
||||
icon: 'shopping-cart',
|
||||
}),
|
||||
])
|
||||
const createPostWithCategoriesMutation = `
|
||||
mutation($title: String!, $content: String!, $categoryIds: [ID]) {
|
||||
CreatePost(title: $title, content: $content, categoryIds: $categoryIds) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
const creatPostWithCategoriesVariables = {
|
||||
title: postTitle,
|
||||
content: postContent,
|
||||
categoryIds: ['cat9', 'cat4', 'cat15'],
|
||||
}
|
||||
const postQueryWithCategories = `
|
||||
query($id: ID) {
|
||||
Post(id: $id) {
|
||||
categories {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const expected = {
|
||||
Post: [
|
||||
{
|
||||
categories: [
|
||||
{ id: expect.any(String) },
|
||||
{ id: expect.any(String) },
|
||||
{ id: expect.any(String) },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
const expected = [{ id: 'cat9' }, { id: 'cat4' }, { id: 'cat15' }]
|
||||
const postWithCategories = await client.request(
|
||||
createPostWithCategoriesMutation,
|
||||
creatPostWithCategoriesVariables,
|
||||
@ -158,27 +152,15 @@ describe('CreatePost', () => {
|
||||
}
|
||||
await expect(
|
||||
client.request(postQueryWithCategories, postQueryWithCategoriesVariables),
|
||||
).resolves.toEqual(expect.objectContaining(expected))
|
||||
).resolves.toEqual({ Post: [{ categories: expect.arrayContaining(expected) }] })
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('UpdatePost', () => {
|
||||
const mutation = `
|
||||
mutation($id: ID!, $content: String) {
|
||||
UpdatePost(id: $id, content: $content) {
|
||||
id
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
let variables = {
|
||||
id: 'p1',
|
||||
content: 'New content',
|
||||
}
|
||||
|
||||
let updatePostMutation
|
||||
let updatePostVariables
|
||||
beforeEach(async () => {
|
||||
const asAuthor = Factory()
|
||||
await asAuthor.create('User', {
|
||||
@ -191,14 +173,32 @@ describe('UpdatePost', () => {
|
||||
})
|
||||
await asAuthor.create('Post', {
|
||||
id: 'p1',
|
||||
content: 'Old content',
|
||||
title: oldTitle,
|
||||
content: oldContent,
|
||||
})
|
||||
updatePostMutation = `
|
||||
mutation($id: ID!, $title: String!, $content: String!, $categoryIds: [ID]) {
|
||||
UpdatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) {
|
||||
id
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
updatePostVariables = {
|
||||
id: 'p1',
|
||||
title: newTitle,
|
||||
content: newContent,
|
||||
categoryIds: null,
|
||||
}
|
||||
})
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
client = new GraphQLClient(host)
|
||||
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
||||
await expect(client.request(updatePostMutation, updatePostVariables)).rejects.toThrow(
|
||||
'Not Authorised',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -210,7 +210,9 @@ describe('UpdatePost', () => {
|
||||
})
|
||||
|
||||
it('throws authorization error', async () => {
|
||||
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
||||
await expect(client.request(updatePostMutation, updatePostVariables)).rejects.toThrow(
|
||||
'Not Authorised',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -222,8 +224,59 @@ describe('UpdatePost', () => {
|
||||
})
|
||||
|
||||
it('updates a post', async () => {
|
||||
const expected = { UpdatePost: { id: 'p1', content: 'New content' } }
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
||||
const expected = { UpdatePost: { id: 'p1', content: newContent } }
|
||||
await expect(client.request(updatePostMutation, updatePostVariables)).resolves.toEqual(
|
||||
expected,
|
||||
)
|
||||
})
|
||||
|
||||
describe('categories', () => {
|
||||
let postWithCategories
|
||||
beforeEach(async () => {
|
||||
await Promise.all([
|
||||
factory.create('Category', {
|
||||
id: 'cat9',
|
||||
name: 'Democracy & Politics',
|
||||
icon: 'university',
|
||||
}),
|
||||
factory.create('Category', {
|
||||
id: 'cat4',
|
||||
name: 'Environment & Nature',
|
||||
icon: 'tree',
|
||||
}),
|
||||
factory.create('Category', {
|
||||
id: 'cat15',
|
||||
name: 'Consumption & Sustainability',
|
||||
icon: 'shopping-cart',
|
||||
}),
|
||||
factory.create('Category', {
|
||||
id: 'cat27',
|
||||
name: 'Animal Protection',
|
||||
icon: 'paw',
|
||||
}),
|
||||
])
|
||||
postWithCategories = await client.request(
|
||||
createPostWithCategoriesMutation,
|
||||
creatPostWithCategoriesVariables,
|
||||
)
|
||||
updatePostVariables = {
|
||||
id: postWithCategories.CreatePost.id,
|
||||
title: newTitle,
|
||||
content: newContent,
|
||||
categoryIds: ['cat27'],
|
||||
}
|
||||
})
|
||||
|
||||
it('allows a user to update the categories of a post', async () => {
|
||||
await client.request(updatePostMutation, updatePostVariables)
|
||||
const expected = [{ id: 'cat27' }]
|
||||
const postQueryWithCategoriesVariables = {
|
||||
id: postWithCategories.CreatePost.id,
|
||||
}
|
||||
await expect(
|
||||
client.request(postQueryWithCategories, postQueryWithCategoriesVariables),
|
||||
).resolves.toEqual({ Post: [{ categories: expect.arrayContaining(expected) }] })
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -58,6 +58,24 @@ type Mutation {
|
||||
categoryIds: [ID]
|
||||
contentExcerpt: String
|
||||
): Post
|
||||
UpdatePost(
|
||||
id: ID!
|
||||
activityId: String
|
||||
objectId: String
|
||||
title: String!
|
||||
slug: String
|
||||
content: String!
|
||||
contentExcerpt: String
|
||||
image: String
|
||||
imageUpload: Upload
|
||||
visibility: Visibility
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
language: String
|
||||
categoryIds: [ID]
|
||||
): Post
|
||||
DeleteUser(id: ID!, resource: [Deletable]): User
|
||||
}
|
||||
|
||||
|
||||
@ -30,6 +30,9 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
existingCategoryIds: { type: Array, default: () => [] },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
categories: null,
|
||||
@ -49,19 +52,28 @@ export default {
|
||||
selectedCategoryIds(categoryIds) {
|
||||
this.$emit('updateCategories', categoryIds)
|
||||
},
|
||||
existingCategoryIds: {
|
||||
immediate: true,
|
||||
handler: function(existingCategoryIds) {
|
||||
if (!existingCategoryIds || !existingCategoryIds.length) {
|
||||
return
|
||||
}
|
||||
this.selectedCategoryIds = existingCategoryIds
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleCategory(id) {
|
||||
const index = this.selectedCategoryIds.indexOf(id)
|
||||
if (index > -1) {
|
||||
this.selectedCategoryIds.splice(index)
|
||||
this.selectedCategoryIds.splice(index, 1)
|
||||
} else {
|
||||
this.selectedCategoryIds.push(id)
|
||||
}
|
||||
},
|
||||
isActive(id) {
|
||||
const activeCategory = this.selectedCategoryIds.find(categoryId => categoryId === id)
|
||||
if (activeCategory) {
|
||||
const index = this.selectedCategoryIds.indexOf(id)
|
||||
if (index > -1) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { config, mount, createLocalVue } from '@vue/test-utils'
|
||||
import ContributionForm from './index.vue'
|
||||
import ContributionForm from './ContributionForm.vue'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Vuex from 'vuex'
|
||||
import PostMutations from '~/graphql/PostMutations.js'
|
||||
@ -29,7 +29,7 @@ describe('ContributionForm.vue', () => {
|
||||
file: { filename: 'avataar.svg', previewElement: '' },
|
||||
url: 'someUrlToImage',
|
||||
}
|
||||
|
||||
const image = '/uploads/1562010976466-avataaars'
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
@ -115,6 +115,7 @@ describe('ContributionForm.vue', () => {
|
||||
id: null,
|
||||
categoryIds: null,
|
||||
imageUpload: null,
|
||||
image: null,
|
||||
},
|
||||
}
|
||||
postTitleInput = wrapper.find('.ds-input')
|
||||
@ -143,6 +144,8 @@ describe('ContributionForm.vue', () => {
|
||||
const categoryIds = ['cat12', 'cat15', 'cat37']
|
||||
expectedParams.variables.categoryIds = categoryIds
|
||||
wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds)
|
||||
await wrapper.find('form').trigger('submit')
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
||||
})
|
||||
|
||||
it('supports adding a teaser image', async () => {
|
||||
@ -197,7 +200,8 @@ describe('ContributionForm.vue', () => {
|
||||
title: 'dies ist ein Post',
|
||||
content: 'auf Deutsch geschrieben',
|
||||
language: 'de',
|
||||
imageUpload,
|
||||
image,
|
||||
categories: [{ id: 'cat12', name: 'Democracy & Politics' }],
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
@ -227,8 +231,9 @@ describe('ContributionForm.vue', () => {
|
||||
content: postContent,
|
||||
language: propsData.contribution.language,
|
||||
id: propsData.contribution.id,
|
||||
categoryIds: null,
|
||||
imageUpload,
|
||||
categoryIds: ['cat12'],
|
||||
image,
|
||||
imageUpload: null,
|
||||
},
|
||||
}
|
||||
postTitleInput = wrapper.find('.ds-input')
|
||||
@ -237,6 +242,17 @@ describe('ContributionForm.vue', () => {
|
||||
await wrapper.find('form').trigger('submit')
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
||||
})
|
||||
|
||||
it('supports updateing categories', async () => {
|
||||
const categoryIds = ['cat3', 'cat51', 'cat37']
|
||||
postTitleInput = wrapper.find('.ds-input')
|
||||
postTitleInput.setValue(postTitle)
|
||||
wrapper.vm.updateEditorContent(postContent)
|
||||
expectedParams.variables.categoryIds = categoryIds
|
||||
wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds)
|
||||
await wrapper.find('form').trigger('submit')
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -14,7 +14,11 @@
|
||||
<hc-editor :users="users" :value="form.content" @input="updateEditorContent" />
|
||||
</no-ssr>
|
||||
<ds-space margin-bottom="xxx-large" />
|
||||
<hc-categories-select model="categories" @updateCategories="updateCategories" />
|
||||
<hc-categories-select
|
||||
model="categoryIds"
|
||||
@updateCategories="updateCategories"
|
||||
:existingCategoryIds="form.categoryIds"
|
||||
/>
|
||||
<ds-flex class="contribution-form-footer">
|
||||
<ds-flex-item :width="{ base: '10%', sm: '10%', md: '10%', lg: '15%' }" />
|
||||
<ds-flex-item :width="{ base: '80%', sm: '30%', md: '30%', lg: '20%' }">
|
||||
@ -33,7 +37,7 @@
|
||||
:disabled="loading || disabled"
|
||||
ghost
|
||||
class="cancel-button"
|
||||
@click="$router.back()"
|
||||
@click.prevent="$router.back()"
|
||||
>
|
||||
{{ $t('actions.cancel') }}
|
||||
</ds-button>
|
||||
@ -77,6 +81,7 @@ export default {
|
||||
title: '',
|
||||
content: '',
|
||||
teaserImage: null,
|
||||
image: null,
|
||||
language: null,
|
||||
languageOptions: [],
|
||||
categoryIds: null,
|
||||
@ -103,7 +108,8 @@ export default {
|
||||
this.slug = contribution.slug
|
||||
this.form.content = contribution.content
|
||||
this.form.title = contribution.title
|
||||
this.form.teaserImage = contribution.imageUpload
|
||||
this.form.image = contribution.image
|
||||
this.form.categoryIds = this.categoryIds(contribution.categories)
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -121,7 +127,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
const { title, content, teaserImage, categoryIds } = this.form
|
||||
const { title, content, image, teaserImage, categoryIds } = this.form
|
||||
let language
|
||||
if (this.form.language) {
|
||||
language = this.form.language.value
|
||||
@ -140,6 +146,7 @@ export default {
|
||||
content,
|
||||
categoryIds,
|
||||
language,
|
||||
image,
|
||||
imageUpload: teaserImage,
|
||||
},
|
||||
})
|
||||
@ -175,6 +182,13 @@ export default {
|
||||
addTeaserImage(file) {
|
||||
this.form.teaserImage = file
|
||||
},
|
||||
categoryIds(categories) {
|
||||
let categoryIds = []
|
||||
categories.map(categoryId => {
|
||||
categoryIds.push(categoryId.id)
|
||||
})
|
||||
return categoryIds
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
User: {
|
||||
@ -18,10 +18,11 @@ export default () => {
|
||||
imageUpload: $imageUpload
|
||||
) {
|
||||
title
|
||||
slug
|
||||
content
|
||||
contentExcerpt
|
||||
language
|
||||
imageUpload
|
||||
image
|
||||
}
|
||||
}
|
||||
`,
|
||||
@ -32,6 +33,8 @@ export default () => {
|
||||
$content: String!
|
||||
$language: String
|
||||
$imageUpload: Upload
|
||||
$categoryIds: [ID]
|
||||
$image: String
|
||||
) {
|
||||
UpdatePost(
|
||||
id: $id
|
||||
@ -39,6 +42,8 @@ export default () => {
|
||||
content: $content
|
||||
language: $language
|
||||
imageUpload: $imageUpload
|
||||
categoryIds: $categoryIds
|
||||
image: $image
|
||||
) {
|
||||
id
|
||||
title
|
||||
@ -46,7 +51,7 @@ export default () => {
|
||||
content
|
||||
contentExcerpt
|
||||
language
|
||||
imageUpload
|
||||
image
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HcContributionForm from '~/components/ContributionForm'
|
||||
import HcContributionForm from '~/components/ContributionForm/ContributionForm'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import HcContributionForm from '~/components/ContributionForm'
|
||||
import HcContributionForm from '~/components/ContributionForm/ContributionForm'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user