Return pinnedAt date from pinPost resolver/clean up

- it's good to return the pinnedAt date for ordering
- move test to a better describe block
- remove unneeded outdated variables from graphql/PostQuery UpdatePost
- fix indentation in Post.gql
- fix pinnedAt to return pinned.createdAt, not post.createdAt

Co-authored-by: Mike Aono <aonomike@gmail.com>
This commit is contained in:
mattwr18 2019-10-18 15:10:26 +02:00
parent 411bbabcd5
commit be0c8044e8
6 changed files with 186 additions and 171 deletions

View File

@ -86,6 +86,7 @@ export default function Resolver(type, options = {}) {
} }
return resolvers return resolvers
} }
const result = { const result = {
...undefinedToNullResolver(undefinedToNull), ...undefinedToNullResolver(undefinedToNull),
...booleanResolver(boolean), ...booleanResolver(boolean),

View File

@ -226,7 +226,7 @@ export default {
return emoted return emoted
}, },
pinPost: async (_parent, params, context, _resolveInfo) => { pinPost: async (_parent, params, context, _resolveInfo) => {
let pinnedPost let pinnedPostWithNestedAttributes
const { driver, user } = context const { driver, user } = context
const session = driver.session() const session = driver.session()
const { id: userId } = user const { id: userId } = user
@ -248,19 +248,27 @@ export default {
` `
MATCH (user:User {id: $userId}) WHERE user.role = 'admin' MATCH (user:User {id: $userId}) WHERE user.role = 'admin'
MATCH (post:Post {id: $params.id}) MATCH (post:Post {id: $params.id})
MERGE (user)-[:PINNED {createdAt: toString(datetime())}]->(post) MERGE (user)-[pinned:PINNED {createdAt: toString(datetime())}]->(post)
RETURN post RETURN post, pinned.createdAt as pinnedAt
`, `,
{ userId, params }, { userId, params },
) )
return pinPostTransactionResponse.records.map(record => record.get('post').properties) return pinPostTransactionResponse.records.map(record => ({
pinnedPost: record.get('post').properties,
pinnedAt: record.get('pinnedAt'),
}))
}) })
try { try {
;[pinnedPost] = await writeTxResultPromise const [transactionResult] = await writeTxResultPromise
const { pinnedPost, pinnedAt } = transactionResult
pinnedPostWithNestedAttributes = {
...pinnedPost,
pinnedAt
}
} finally { } finally {
session.close() session.close()
} }
return pinnedPost return pinnedPostWithNestedAttributes
}, },
unpinPost: async (_parent, params, context, _resolveInfo) => { unpinPost: async (_parent, params, context, _resolveInfo) => {
let unpinnedPost let unpinnedPost

View File

@ -581,6 +581,7 @@ describe('UpdatePost', () => {
} }
createdAt createdAt
updatedAt updatedAt
pinnedAt
} }
} }
` `
@ -607,7 +608,7 @@ describe('UpdatePost', () => {
}) })
}) })
describe('moderator cannot pin posts', () => { describe('moderators cannot pin posts', () => {
let moderator let moderator
beforeEach(async () => { beforeEach(async () => {
moderator = await user.update({ role: 'moderator', updatedAt: new Date().toISOString() }) moderator = await user.update({ role: 'moderator', updatedAt: new Date().toISOString() })
@ -664,6 +665,22 @@ describe('UpdatePost', () => {
expected, expected,
) )
}) })
it('sets createdAt date for PINNED', async () => {
variables = { ...variables, id: 'created-and-pinned-by-same-admin' }
const expected = {
data: {
pinPost: {
id: 'created-and-pinned-by-same-admin',
pinnedAt: expect.any(String)
},
},
errors: undefined,
}
await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject(
expected,
)
})
}) })
describe('post created by another admin', () => { describe('post created by another admin', () => {
@ -742,20 +759,13 @@ describe('UpdatePost', () => {
variables = { ...variables, id: 'only-pinned-post' } variables = { ...variables, id: 'only-pinned-post' }
await mutate({ mutation: pinPostMutation, variables }) await mutate({ mutation: pinPostMutation, variables })
pinnedPost = await neode.cypher( pinnedPost = await neode.cypher(
`MATCH ()-[relationship:PINNED]->(post:Post) RETURN post, relationship`, `MATCH ()-[pinned:PINNED]->(post:Post) RETURN post, pinned`,
) )
}) })
it('leaves only one pinned post at a time', async () => { it('leaves only one pinned post at a time', async () => {
expect(pinnedPost.records).toHaveLength(1) expect(pinnedPost.records).toHaveLength(1)
}) })
it('sets createdAt date for PINNED', () => {
const [pinnedPostCreatedAt] = pinnedPost.records.map(record => {
return record.get('relationship').properties.createdAt
})
expect(pinnedPostCreatedAt).toEqual(expect.any(String))
})
}) })
describe('PostOrdering', () => { describe('PostOrdering', () => {
@ -787,7 +797,7 @@ describe('UpdatePost', () => {
name: 'Admin', name: 'Admin',
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
}) })
await admin.relateTo(pinnedPost, 'pinned', { createdAt: newDate.toISOString() }) await admin.relateTo(pinnedPost, 'pinned')
}) })
it('pinned post appear first even when created before other posts', async () => { it('pinned post appear first even when created before other posts', async () => {
@ -870,7 +880,7 @@ describe('UpdatePost', () => {
}) })
}) })
describe('moderator cannot unpin posts', () => { describe('moderators cannot unpin posts', () => {
let moderator let moderator
beforeEach(async () => { beforeEach(async () => {
moderator = await user.update({ role: 'moderator', updatedAt: new Date().toISOString() }) moderator = await user.update({ role: 'moderator', updatedAt: new Date().toISOString() })

View File

@ -17,7 +17,7 @@ type Post {
updatedAt: String updatedAt: String
language: String language: String
pinnedAt: String @cypher( pinnedAt: String @cypher(
statement: "MATCH (this)<-[pinned:PINNED]-(:User) WHERE NOT this.deleted = true AND NOT this.disabled = true RETURN this.createdAt" statement: "MATCH (this)<-[pinned:PINNED]-(:User) WHERE NOT this.deleted = true AND NOT this.disabled = true RETURN pinned.createdAt"
) )
pinnedBy: User @relation(name:"PINNED", direction: "IN") pinnedBy: User @relation(name:"PINNED", direction: "IN")
relatedContributions: [Post]! relatedContributions: [Post]!

View File

@ -1,180 +1,180 @@
type User { type User {
id: ID! id: ID!
actorId: String actorId: String
name: String name: String
email: String! @cypher(statement: "MATCH (this)-[: PRIMARY_EMAIL]->(e: EmailAddress) RETURN e.email") email: String! @cypher(statement: "MATCH (this)-[: PRIMARY_EMAIL]->(e: EmailAddress) RETURN e.email")
slug: String! slug: String!
avatar: String avatar: String
coverImg: String coverImg: String
deleted: Boolean deleted: Boolean
disabled: Boolean disabled: Boolean
disabledBy: User @relation(name: "DISABLED", direction: "IN") disabledBy: User @relation(name: "DISABLED", direction: "IN")
role: UserGroup! role: UserGroup!
publicKey: String publicKey: String
invitedBy: User @relation(name: "INVITED", direction: "IN") invitedBy: User @relation(name: "INVITED", direction: "IN")
invited: [User] @relation(name: "INVITED", direction: "OUT") invited: [User] @relation(name: "INVITED", direction: "OUT")
location: Location @cypher(statement: "MATCH (this)-[: IS_IN]->(l: Location) RETURN l") location: Location @cypher(statement: "MATCH (this)-[: IS_IN]->(l: Location) RETURN l")
locationName: String locationName: String
about: String about: String
socialMedia: [SocialMedia]! @relation(name: "OWNED_BY", direction: "IN") socialMedia: [SocialMedia]! @relation(name: "OWNED_BY", direction: "IN")
# createdAt: DateTime # createdAt: DateTime
# updatedAt: DateTime # updatedAt: DateTime
createdAt: String createdAt: String
updatedAt: String updatedAt: String
termsAndConditionsAgreedVersion: String termsAndConditionsAgreedVersion: String
termsAndConditionsAgreedAt: String termsAndConditionsAgreedAt: String
allowEmbedIframes: Boolean allowEmbedIframes: Boolean
friends: [User]! @relation(name: "FRIENDS", direction: "BOTH") friends: [User]! @relation(name: "FRIENDS", direction: "BOTH")
friendsCount: Int! @cypher(statement: "MATCH (this)<-[: FRIENDS]->(r: User) RETURN COUNT(DISTINCT r)") friendsCount: Int! @cypher(statement: "MATCH (this)<-[: FRIENDS]->(r: User) RETURN COUNT(DISTINCT r)")
following: [User]! @relation(name: "FOLLOWS", direction: "OUT") following: [User]! @relation(name: "FOLLOWS", direction: "OUT")
followingCount: Int! @cypher(statement: "MATCH (this)-[: FOLLOWS]->(r: User) RETURN COUNT(DISTINCT r)") followingCount: Int! @cypher(statement: "MATCH (this)-[: FOLLOWS]->(r: User) RETURN COUNT(DISTINCT r)")
followedBy: [User]! @relation(name: "FOLLOWS", direction: "IN") followedBy: [User]! @relation(name: "FOLLOWS", direction: "IN")
followedByCount: Int! @cypher(statement: "MATCH (this)<-[: FOLLOWS]-(r: User) RETURN COUNT(DISTINCT r)") followedByCount: Int! @cypher(statement: "MATCH (this)<-[: FOLLOWS]-(r: User) RETURN COUNT(DISTINCT r)")
# Is the currently logged in user following that user? # Is the currently logged in user following that user?
followedByCurrentUser: Boolean! @cypher( followedByCurrentUser: Boolean! @cypher(
statement: """ statement: """
MATCH (this)<-[: FOLLOWS]-(u: User { id: $cypherParams.currentUserId}) MATCH (this)<-[: FOLLOWS]-(u: User { id: $cypherParams.currentUserId})
RETURN COUNT(u) >= 1 RETURN COUNT(u) >= 1
""" """
) )
isBlocked: Boolean! @cypher( isBlocked: Boolean! @cypher(
statement: """ statement: """
MATCH (this)<-[: BLOCKED]-(u: User { id: $cypherParams.currentUserId}) MATCH (this)<-[: BLOCKED]-(u: User { id: $cypherParams.currentUserId})
RETURN COUNT(u) >= 1 RETURN COUNT(u) >= 1
""" """
) )
# contributions: [WrittenPost]! # contributions: [WrittenPost]!
# contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]! # contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]!
# @cypher( # @cypher(
# statement: "MATCH (this)-[w:WROTE]->(p:Post) RETURN p as Post, w.timestamp as timestamp" # statement: "MATCH (this)-[w:WROTE]->(p:Post) RETURN p as Post, w.timestamp as timestamp"
# ) # )
contributions: [Post]! @relation(name: "WROTE", direction: "OUT") contributions: [Post]! @relation(name: "WROTE", direction: "OUT")
contributionsCount: Int! @cypher( contributionsCount: Int! @cypher(
statement: """ statement: """
MATCH (this)-[: WROTE]->(r: Post) MATCH (this)-[: WROTE]->(r: Post)
WHERE NOT r.deleted = true AND NOT r.disabled = true WHERE NOT r.deleted = true AND NOT r.disabled = true
RETURN COUNT(r) RETURN COUNT(r)
""" """
) )
comments: [Comment]! @relation(name: "WROTE", direction: "OUT") comments: [Comment]! @relation(name: "WROTE", direction: "OUT")
commentedCount: Int! @cypher(statement: "MATCH (this)-[: WROTE]->(: Comment)-[: COMMENTS]->(p: Post) WHERE NOT p.deleted = true AND NOT p.disabled = true RETURN COUNT(DISTINCT(p))") commentedCount: Int! @cypher(statement: "MATCH (this)-[: WROTE]->(: Comment)-[: COMMENTS]->(p: Post) WHERE NOT p.deleted = true AND NOT p.disabled = true RETURN COUNT(DISTINCT(p))")
shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT") shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT")
shoutedCount: Int! @cypher(statement: "MATCH (this)-[: SHOUTED]->(r: Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)") shoutedCount: Int! @cypher(statement: "MATCH (this)-[: SHOUTED]->(r: Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)")
categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT") categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT")
badges: [Badge]! @relation(name: "REWARDED", direction: "IN") badges: [Badge]! @relation(name: "REWARDED", direction: "IN")
badgesCount: Int! @cypher(statement: "MATCH (this)<-[: REWARDED]-(r: Badge) RETURN COUNT(r)") badgesCount: Int! @cypher(statement: "MATCH (this)<-[: REWARDED]-(r: Badge) RETURN COUNT(r)")
emotions: [EMOTED] emotions: [EMOTED]
} }
input _UserFilter { input _UserFilter {
AND: [_UserFilter!] AND: [_UserFilter!]
OR: [_UserFilter!] OR: [_UserFilter!]
name_contains: String name_contains: String
about_contains: String about_contains: String
slug_contains: String slug_contains: String
id: ID id: ID
id_not: ID id_not: ID
id_in: [ID!] id_in: [ID!]
id_not_in: [ID!] id_not_in: [ID!]
id_contains: ID id_contains: ID
id_not_contains: ID id_not_contains: ID
id_starts_with: ID id_starts_with: ID
id_not_starts_with: ID id_not_starts_with: ID
id_ends_with: ID id_ends_with: ID
id_not_ends_with: ID id_not_ends_with: ID
friends: _UserFilter friends: _UserFilter
friends_not: _UserFilter friends_not: _UserFilter
friends_in: [_UserFilter!] friends_in: [_UserFilter!]
friends_not_in: [_UserFilter!] friends_not_in: [_UserFilter!]
friends_some: _UserFilter friends_some: _UserFilter
friends_none: _UserFilter friends_none: _UserFilter
friends_single: _UserFilter friends_single: _UserFilter
friends_every: _UserFilter friends_every: _UserFilter
following: _UserFilter following: _UserFilter
following_not: _UserFilter following_not: _UserFilter
following_in: [_UserFilter!] following_in: [_UserFilter!]
following_not_in: [_UserFilter!] following_not_in: [_UserFilter!]
following_some: _UserFilter following_some: _UserFilter
following_none: _UserFilter following_none: _UserFilter
following_single: _UserFilter following_single: _UserFilter
following_every: _UserFilter following_every: _UserFilter
followedBy: _UserFilter followedBy: _UserFilter
followedBy_not: _UserFilter followedBy_not: _UserFilter
followedBy_in: [_UserFilter!] followedBy_in: [_UserFilter!]
followedBy_not_in: [_UserFilter!] followedBy_not_in: [_UserFilter!]
followedBy_some: _UserFilter followedBy_some: _UserFilter
followedBy_none: _UserFilter followedBy_none: _UserFilter
followedBy_single: _UserFilter followedBy_single: _UserFilter
followedBy_every: _UserFilter followedBy_every: _UserFilter
role_in: [UserGroup!] role_in: [UserGroup!]
} }
type Query { type Query {
User( User(
id: ID id: ID
email: String email: String
actorId: String actorId: String
name: String name: String
slug: String slug: String
avatar: String avatar: String
coverImg: String coverImg: String
role: UserGroup role: UserGroup
locationName: String locationName: String
about: String about: String
createdAt: String createdAt: String
updatedAt: String updatedAt: String
friendsCount: Int friendsCount: Int
followingCount: Int followingCount: Int
followedByCount: Int followedByCount: Int
followedByCurrentUser: Boolean followedByCurrentUser: Boolean
contributionsCount: Int contributionsCount: Int
commentedCount: Int commentedCount: Int
shoutedCount: Int shoutedCount: Int
badgesCount: Int badgesCount: Int
first: Int first: Int
offset: Int offset: Int
orderBy: [_UserOrdering] orderBy: [_UserOrdering]
filter: _UserFilter filter: _UserFilter
): [User] ): [User]
blockedUsers: [User] blockedUsers: [User]
currentUser: User currentUser: User
} }
type Mutation { type Mutation {
UpdateUser ( UpdateUser (
id: ID! id: ID!
name: String name: String
email: String email: String
slug: String slug: String
avatar: String avatar: String
coverImg: String coverImg: String
avatarUpload: Upload avatarUpload: Upload
locationName: String locationName: String
about: String about: String
termsAndConditionsAgreedVersion: String termsAndConditionsAgreedVersion: String
termsAndConditionsAgreedAt: String termsAndConditionsAgreedAt: String
allowEmbedIframes: Boolean allowEmbedIframes: Boolean
): User ): User
DeleteUser(id: ID!, resource: [Deletable]): User DeleteUser(id: ID!, resource: [Deletable]): User
block(id: ID!): User block(id: ID!): User
unblock(id: ID!): User unblock(id: ID!): User
} }

View File

@ -34,8 +34,6 @@ export default () => {
$imageUpload: Upload $imageUpload: Upload
$categoryIds: [ID] $categoryIds: [ID]
$image: String $image: String
$pinned: Boolean
$unpinned: Boolean
) { ) {
UpdatePost( UpdatePost(
id: $id id: $id
@ -45,8 +43,6 @@ export default () => {
imageUpload: $imageUpload imageUpload: $imageUpload
categoryIds: $categoryIds categoryIds: $categoryIds
image: $image image: $image
pinned: $pinned
unpinned: $unpinned
) { ) {
id id
title title