Merge pull request #6198 from Ocelot-Social-Community/optional-event-parameters

feat(backend): event parameters
This commit is contained in:
Moriz Wahl 2023-04-13 12:42:01 +02:00 committed by GitHub
commit 738e7ad64c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 393 additions and 25 deletions

View File

@ -12,6 +12,7 @@ export const createPostMutation = () => {
$categoryIds: [ID]
$groupId: ID
$postType: PostType
$eventInput: _EventInput
) {
CreatePost(
id: $id
@ -21,6 +22,7 @@ export const createPostMutation = () => {
categoryIds: $categoryIds
groupId: $groupId
postType: $postType
eventInput: $eventInput
) {
id
slug
@ -35,6 +37,13 @@ export const createPostMutation = () => {
categories {
id
}
eventStart
eventLocationName
eventVenue
eventLocation {
lng
lat
}
}
}
`

View File

@ -0,0 +1,35 @@
import { UserInputError } from 'apollo-server'
export const validateEventParams = (params) => {
if (params.postType && params.postType === 'Event') {
const { eventInput } = params
validateEventDate(eventInput.eventStart)
params.eventStart = eventInput.eventStart
if (eventInput.eventLocation && !eventInput.eventVenue) {
throw new UserInputError('Event venue must be present if event location is given!')
}
params.eventVenue = eventInput.eventVenue
params.eventLocation = eventInput.eventLocation
}
delete params.eventInput
let locationName
if (params.eventLocation) {
params.eventLocationName = params.eventLocation
locationName = params.eventLocation
} else {
params.eventLocationName = null
locationName = null
}
delete params.eventLocation
return locationName
}
const validateEventDate = (dateString) => {
const date = new Date(dateString)
if (date.toString() === 'Invalid Date')
throw new UserInputError('Event start date must be a valid date!')
const now = new Date()
if (date.getTime() < now.getTime()) {
throw new UserInputError('Event start date must be in the future!')
}
}

View File

@ -7,6 +7,8 @@ import Resolver from './helpers/Resolver'
import { filterForMutedUsers } from './helpers/filterForMutedUsers'
import { filterInvisiblePosts } from './helpers/filterInvisiblePosts'
import { filterPostsOfMyGroups } from './helpers/filterPostsOfMyGroups'
import { validateEventParams } from './helpers/events'
import { createOrUpdateLocations } from './users/location'
import CONFIG from '../../config'
const maintainPinnedPosts = (params) => {
@ -81,6 +83,9 @@ export default {
CreatePost: async (_parent, params, context, _resolveInfo) => {
const { categoryIds, groupId } = params
const { image: imageInput } = params
const locationName = validateEventParams(params)
delete params.categoryIds
delete params.image
delete params.groupId
@ -143,6 +148,9 @@ export default {
})
try {
const post = await writeTxResultPromise
if (locationName) {
await createOrUpdateLocations('Post', post.id, locationName, session)
}
return post
} catch (e) {
if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
@ -155,6 +163,9 @@ export default {
UpdatePost: async (_parent, params, context, _resolveInfo) => {
const { categoryIds } = params
const { image: imageInput } = params
const locationName = validateEventParams(params)
delete params.categoryIds
delete params.image
const session = context.driver.session()
@ -206,6 +217,9 @@ export default {
return post
})
const post = await writeTxResultPromise
if (locationName) {
await createOrUpdateLocations('Post', post.id, locationName, session)
}
return post
} finally {
session.close()
@ -392,7 +406,17 @@ export default {
},
Post: {
...Resolver('Post', {
undefinedToNull: ['activityId', 'objectId', 'language', 'pinnedAt', 'pinned'],
undefinedToNull: [
'activityId',
'objectId',
'language',
'pinnedAt',
'pinned',
'eventVenue',
'eventLocation',
'eventLocationName',
'eventStart',
],
hasMany: {
tags: '-[:TAGGED]->(related:Tag)',
categories: '-[:CATEGORIZED]->(related:Category)',
@ -405,6 +429,7 @@ export default {
pinnedBy: '<-[:PINNED]-(related:User)',
image: '-[:HERO_IMAGE]->(related:Image)',
group: '-[:IN]->(related:Group)',
eventLocation: '-[:IS_IN]->(related:Location)',
},
count: {
commentsCount:

View File

@ -312,19 +312,6 @@ describe('CreatePost', () => {
})
})
describe('with post type "Event"', () => {
it('has label "Event" set', async () => {
await expect(
mutate({
mutation: createPostMutation(),
variables: { ...variables, postType: 'Event' },
}),
).resolves.toMatchObject({
data: { CreatePost: { postType: ['Event'] } },
})
})
})
describe('with invalid post type', () => {
it('throws an error', async () => {
await expect(
@ -342,6 +329,160 @@ describe('CreatePost', () => {
})
})
})
describe('with post type "Event"', () => {
describe('without event start date', () => {
it('throws an error', async () => {
await expect(
mutate({
mutation: createPostMutation(),
variables: {
...variables,
postType: 'Event',
},
}),
).resolves.toMatchObject({
errors: [
{
message: "Cannot read properties of undefined (reading 'eventStart')",
},
],
})
})
})
describe('with invalid event start date', () => {
it('throws an error', async () => {
await expect(
mutate({
mutation: createPostMutation(),
variables: {
...variables,
postType: 'Event',
eventInput: {
eventStart: 'no date',
},
},
}),
).resolves.toMatchObject({
errors: [
{
message: 'Event start date must be a valid date!',
},
],
})
})
})
describe('with event start date in the past', () => {
it('throws an error', async () => {
const now = new Date()
await expect(
mutate({
mutation: createPostMutation(),
variables: {
...variables,
postType: 'Event',
eventInput: {
eventStart: new Date(now.getFullYear(), now.getMonth() - 1).toISOString(),
},
},
}),
).resolves.toMatchObject({
errors: [
{
message: 'Event start date must be in the future!',
},
],
})
})
})
describe('event location is given but event venue is missing', () => {
it('throws an error', async () => {
const now = new Date()
await expect(
mutate({
mutation: createPostMutation(),
variables: {
...variables,
postType: 'Event',
eventInput: {
eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(),
eventLocation: 'Berlin',
},
},
}),
).resolves.toMatchObject({
errors: [
{
message: 'Event venue must be present if event location is given!',
},
],
})
})
})
describe('valid event input without location', () => {
it('has label "Event" set', async () => {
const now = new Date()
await expect(
mutate({
mutation: createPostMutation(),
variables: {
...variables,
postType: 'Event',
eventInput: {
eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(),
},
},
}),
).resolves.toMatchObject({
data: {
CreatePost: {
postType: ['Event'],
eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(),
},
},
errors: undefined,
})
})
})
describe('valid event input with location', () => {
it('has label "Event" set', async () => {
const now = new Date()
await expect(
mutate({
mutation: createPostMutation(),
variables: {
...variables,
postType: 'Event',
eventInput: {
eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(),
eventLocation: 'Leipzig',
eventVenue: 'Connewitzer Kreuz',
},
},
}),
).resolves.toMatchObject({
data: {
CreatePost: {
postType: ['Event'],
eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(),
eventLocationName: 'Leipzig',
eventVenue: 'Connewitzer Kreuz',
eventLocation: {
lng: 12.374733,
lat: 51.340632,
},
},
},
errors: undefined,
})
})
})
})
})
})
@ -355,6 +496,7 @@ describe('UpdatePost', () => {
$image: ImageInput
$categoryIds: [ID]
$postType: PostType
$eventInput: _EventInput
) {
UpdatePost(
id: $id
@ -363,6 +505,7 @@ describe('UpdatePost', () => {
image: $image
categoryIds: $categoryIds
postType: $postType
eventInput: $eventInput
) {
id
title
@ -377,6 +520,13 @@ describe('UpdatePost', () => {
id
}
postType
eventStart
eventLocationName
eventVenue
eventLocation {
lng
lat
}
}
}
`
@ -498,18 +648,153 @@ describe('UpdatePost', () => {
})
})
describe('post type', () => {
it('changes the post type', async () => {
await expect(
mutate({ mutation: updatePostMutation, variables: { ...variables, postType: 'Event' } }),
).resolves.toMatchObject({
data: {
UpdatePost: {
id: newlyCreatedPost.id,
postType: ['Event'],
describe('change post type to event', () => {
describe('with missing event start date', () => {
it('throws an error', async () => {
await expect(
mutate({
mutation: updatePostMutation,
variables: { ...variables, postType: 'Event' },
}),
).resolves.toMatchObject({
errors: [
{
message: "Cannot read properties of undefined (reading 'eventStart')",
},
],
})
})
})
describe('with invalid event start date', () => {
it('throws an error', async () => {
await expect(
mutate({
mutation: updatePostMutation,
variables: {
...variables,
postType: 'Event',
eventInput: {
eventStart: 'no-date',
},
},
}),
).resolves.toMatchObject({
errors: [
{
message: 'Event start date must be a valid date!',
},
],
})
})
})
describe('with event start date in the past', () => {
it('throws an error', async () => {
const now = new Date()
await expect(
mutate({
mutation: updatePostMutation,
variables: {
...variables,
postType: 'Event',
eventInput: {
eventStart: new Date(now.getFullYear(), now.getMonth() - 1).toISOString(),
},
},
}),
).resolves.toMatchObject({
errors: [
{
message: 'Event start date must be in the future!',
},
],
})
})
})
describe('event location is given but event venue is missing', () => {
it('throws an error', async () => {
const now = new Date()
await expect(
mutate({
mutation: updatePostMutation,
variables: {
...variables,
postType: 'Event',
eventInput: {
eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(),
eventLocation: 'Berlin',
},
},
}),
).resolves.toMatchObject({
errors: [
{
message: 'Event venue must be present if event location is given!',
},
],
})
})
})
describe('valid event input without location', () => {
it('has label "Event" set', async () => {
const now = new Date()
await expect(
mutate({
mutation: updatePostMutation,
variables: {
...variables,
postType: 'Event',
eventInput: {
eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(),
},
},
}),
).resolves.toMatchObject({
data: {
UpdatePost: {
postType: ['Event'],
eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(),
},
},
},
errors: undefined,
errors: undefined,
})
})
})
describe('valid event input with location', () => {
it('has label "Event" set', async () => {
const now = new Date()
await expect(
mutate({
mutation: updatePostMutation,
variables: {
...variables,
postType: 'Event',
eventInput: {
eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(),
eventLocation: 'Leipzig',
eventVenue: 'Connewitzer Kreuz',
},
},
}),
).resolves.toMatchObject({
data: {
UpdatePost: {
postType: ['Event'],
eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(),
eventLocationName: 'Leipzig',
eventVenue: 'Connewitzer Kreuz',
eventLocation: {
lng: 12.374733,
lat: 51.340632,
},
},
},
errors: undefined,
})
})
})
})

View File

@ -171,14 +171,26 @@ type Post {
@cypher(statement: "MATCH (this)<-[emoted:EMOTED]-(:User) RETURN COUNT(DISTINCT emoted)")
group: Group @relation(name: "IN", direction: "OUT")
postType: [PostType]
@cypher(statement: "RETURN filter(l IN labels(this) WHERE NOT l = 'Post')")
eventLocationName: String
eventLocation: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l")
eventVenue: String
eventStart: String
}
input _PostInput {
id: ID!
}
input _EventInput {
eventStart: String!
eventLocation: String
eventVenue: String
}
type Mutation {
CreatePost(
id: ID
@ -192,6 +204,7 @@ type Mutation {
contentExcerpt: String
groupId: ID
postType: PostType = Article
eventInput: _EventInput
): Post
UpdatePost(
id: ID!
@ -204,6 +217,7 @@ type Mutation {
language: String
categoryIds: [ID]
postType: PostType
eventInput: _EventInput
): Post
DeletePost(id: ID!): Post
AddPostEmotions(to: _PostInput!, data: _EMOTEDInput!): EMOTED