diff --git a/backend/jest.config.js b/backend/jest.config.js index d1cc7bd3f..a4a0ce1df 100644 --- a/backend/jest.config.js +++ b/backend/jest.config.js @@ -7,7 +7,8 @@ module.exports = { '!**/node_modules/**', '!**/test/**', '!**/build/**', - '!**/src/**/?(*.)+(spec|test).ts?(x)' + '!**/src/**/?(*.)+(spec|test).ts?(x)', + '!**/src/db/migrations/**' ], coverageThreshold: { global: { diff --git a/backend/src/db/migrations/20231017141022-fix-event-dates.ts b/backend/src/db/migrations/20231017141022-fix-event-dates.ts new file mode 100644 index 000000000..3c4302f13 --- /dev/null +++ b/backend/src/db/migrations/20231017141022-fix-event-dates.ts @@ -0,0 +1,68 @@ +import { getDriver } from '../../db/neo4j' + +export const description = ` +Transform event start and end date of format 'YYYY-MM-DD HH:MM:SS' in CEST +to ISOString in UTC. +` + +export async function up(next) { + const driver = getDriver() + const session = driver.session() + const transaction = session.beginTransaction() + + try { + const events = await transaction.run(` + MATCH (event:Event) + WHERE NOT event.eventStart CONTAINS 'T' + RETURN event.id, event.eventStart, event.eventEnd + `) + for (const event of events.records) { + let [id, eventStart, eventEnd] = event + let date = new Date(eventStart) + date.setHours(date.getHours() - 1) + eventStart = date.toISOString() + if (eventEnd) { + date = new Date(eventEnd) + date.setHours(date.getHours() - 1) + eventEnd = date.toISOString() + } + await transaction.run(` + MATCH (e:Event { id: '${id}' }) + SET e.eventStart = '${eventStart}' + SET (CASE WHEN exists(e.eventEnd) THEN e END).eventEnd = '${eventEnd}' + RETURN e + `) + } + await transaction.commit() + next() + } catch (error) { + // eslint-disable-next-line no-console + console.log(error) + await transaction.rollback() + // eslint-disable-next-line no-console + console.log('rolled back') + throw new Error(error) + } finally { + session.close() + } +} + +export async function down(next) { + const driver = getDriver() + const session = driver.session() + const transaction = session.beginTransaction() + + try { + // No sense in running this down + next() + } catch (error) { + // eslint-disable-next-line no-console + console.log(error) + await transaction.rollback() + // eslint-disable-next-line no-console + console.log('rolled back') + throw new Error(error) + } finally { + session.close() + } +} diff --git a/backend/src/schema/resolvers/helpers/events.ts b/backend/src/schema/resolvers/helpers/events.ts index 835088d8c..d4fc1fb11 100644 --- a/backend/src/schema/resolvers/helpers/events.ts +++ b/backend/src/schema/resolvers/helpers/events.ts @@ -34,6 +34,8 @@ const validateEventDate = (dateString) => { const date = new Date(dateString) if (date.toString() === 'Invalid Date') throw new UserInputError('Event start date must be a valid date!') + if (date.toISOString() !== dateString) + throw new UserInputError('Event start date must be in ISO format!') const now = new Date() if (date.getTime() < now.getTime()) { throw new UserInputError('Event start date must be in the future!') @@ -44,6 +46,8 @@ const validateEventEnd = (start, end) => { const endDate = new Date(end) if (endDate.toString() === 'Invalid Date') throw new UserInputError('Event end date must be a valid date!') + if (endDate.toISOString() !== end) + throw new UserInputError('Event end date must be in ISO format!') const startDate = new Date(start) if (endDate < startDate) throw new UserInputError('Event end date must be a after event start date!') diff --git a/backend/src/schema/resolvers/posts.spec.ts b/backend/src/schema/resolvers/posts.spec.ts index 7a549449f..b816e25aa 100644 --- a/backend/src/schema/resolvers/posts.spec.ts +++ b/backend/src/schema/resolvers/posts.spec.ts @@ -374,6 +374,31 @@ describe('CreatePost', () => { }) }) + describe('with event start in no ISO format', () => { + it('throws an error', async () => { + const now = new Date() + const eventStart = new Date(now.getFullYear(), now.getMonth() - 1).toISOString() + await expect( + mutate({ + mutation: createPostMutation(), + variables: { + ...variables, + postType: 'Event', + eventInput: { + eventStart: eventStart.split('T')[0], + }, + }, + }), + ).resolves.toMatchObject({ + errors: [ + { + message: 'Event start date must be in ISO format!', + }, + ], + }) + }) + }) + describe('with event start date in the past', () => { it('throws an error', async () => { const now = new Date() @@ -423,6 +448,32 @@ describe('CreatePost', () => { }) }) + describe('with valid start date and not ISO formated end date', () => { + it('throws an error', async () => { + const now = new Date() + const eventEnd = new Date(now.getFullYear(), now.getMonth() + 2).toISOString() + await expect( + mutate({ + mutation: createPostMutation(), + variables: { + ...variables, + postType: 'Event', + eventInput: { + eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + eventEnd: eventEnd.split('T')[0], + }, + }, + }), + ).resolves.toMatchObject({ + errors: [ + { + message: 'Event end date must be in ISO format!', + }, + ], + }) + }) + }) + describe('with valid start date and end date before start date', () => { it('throws an error', async () => { const now = new Date() diff --git a/webapp/components/ContributionForm/ContributionForm.vue b/webapp/components/ContributionForm/ContributionForm.vue index 0a5eba0cd..5af3d7a99 100644 --- a/webapp/components/ContributionForm/ContributionForm.vue +++ b/webapp/components/ContributionForm/ContributionForm.vue @@ -328,9 +328,9 @@ export default { eventInput() { if (this.createEvent) { return { - eventStart: this.formData.eventStart, + eventStart: new Date(this.formData.eventStart).toISOString(), eventVenue: this.formData.eventVenue, - eventEnd: this.formData.eventEnd, + eventEnd: this.formData.eventEnd ? new Date(this.formData.eventEnd).toISOString() : null, eventIsOnline: this.formData.eventIsOnline, eventLocationName: !this.formData.eventIsOnline ? this.formData.eventLocationName : null, }