Files
API/src/models/calendar/events/events.router.ts
T
Paddy 13a0c07d1b
Jenkins Production Deployment
Read calendar event creators from the admin module, and archive the old ones (#14)
Reviewed-on: #14
Co-authored-by: Patrick Müller <mail@pmueller.me>
Co-committed-by: Patrick Müller <mail@pmueller.me>
2026-09-06 21:12:16 +00:00

1092 lines
44 KiB
TypeScript

/**
* Required External Modules and Interfaces
*/
import express, {Request, Response} from 'express';
import {Event} from './event.interface.js';
import * as EventService from './events.service.js';
import * as iCalService from './icalgenerator.service.js';
import * as CredentialService from './credentials.service.js';
import {requireAppAccess, resolveAccess, AdminAccess} from '../../admin/admin.middleware.js';
import {Guid} from 'guid-typescript';
import logger from '../../../middleware/logger.js';
/**
* Router Definition
*/
export const eventsRouter = express.Router();
/**
* Constants
*/
export const calendarNames = new Map<string, any>([
['public', {id: 1, name: 'Nachklang_calendar'}],
['members', {id: 2, name: 'Nachklang_internal_calendar'}],
['choir', {id: 4, name: 'Nachklang_choir_calendar'}],
['management', {id: 3, name: 'Nachklang_management_calendar'}],
['birthdays', {id: 5, name: 'Nachklang_birthday_calendar'}]
]);
/**
* The gate on everything that writes. Step 4 of
* docs/calendar-auth-migration.md replaced a sessionId/sessionKey pair in the
* query string (DEFERRED_SECURITY.md item 1) with the same session cookie the
* other three apps use, and "any activated @nachklang.art account" with an
* explicit per-user calendar permission.
*/
const requireCalendarAccess = requireAppAccess('calendar');
/** Set by requireCalendarAccess; the writer's admin identity. */
const adminOf = (res: Response): AdminAccess => res.locals.admin as AdminAccess;
/**
* Resolves a signed-in calendar user for the *read* routes, or null.
*
* Reads cannot use the middleware: the same URL serves an anonymous visitor
* (the public calendar the website polls), someone holding a shared password
* (an iCal subscription), and a signed-in editor who should see drafts. So it
* answers "who is this, if anyone?" instead of refusing the request, and each
* handler decides what that means.
*
* A failure to reach the admin database is swallowed for the same reason the
* name lookup in events.service.ts swallows one: it must not be able to take
* the anonymous public calendar down.
*/
const signedInEditor = async (req: Request): Promise<AdminAccess | null> => {
try {
const access = await resolveAccess(req);
if (!access || access.disabled || !access.apps.includes('calendar')) {
return null;
}
return access;
} catch (e: any) {
logger.warn('Calendar: could not resolve the session, continuing as anonymous: ' + e.message);
return null;
}
};
/**
* Controller Definitions
*/
/**
* @swagger
* /calendar/events/{calendar}/json:
* get:
* summary: Get all events from a specific calendar in JSON format
* description: >
* Returns the calendar's events. The public calendar is open to everyone; the
* others need either a signed-in account with the calendar permission - which
* also unlocks drafts - or the calendar's shared password.
* tags:
* - calendar
* parameters:
* - in: path
* name: calendar
* required: true
* schema:
* type: string
* enum: [public, members, choir, management, birthdays]
* description: The name of the calendar to get events from
* - in: query
* name: password
* schema:
* type: string
* description: The calendar's shared password, for callers with no account
* responses:
* 200:
* description: Success
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: '#/components/schemas/Event'
* 400:
* description: Bad request - missing or invalid parameters
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Please state the name of the calendar you want events from.
* 403:
* description: Forbidden - no access to the calendar
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: You do not have access to the specified calendar.
* 500:
* description: Server error
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Internal Server Error. Try again later.
*/
eventsRouter.get('/:calendar/json', async (req: Request, res: Response) => {
try {
// Get request params
let calendarName: string = req.params.calendar as string ?? '';
let password: string = req.query.password as string ?? '';
if (calendarName.length < 1) {
res.status(400).send({'message': 'Please state the name of the calendar you want events from.'});
return;
}
if (!Array.from(calendarNames.keys()).includes(calendarName)) {
res.status(400).send({'message': 'Unknown calendar.'});
return;
}
let calendarId: number = calendarNames.get(calendarName)!.id;
const editor = await signedInEditor(req);
// Not signed in: fall back to the shared password for this calendar.
if (!editor && ! await CredentialService.hasAccess(calendarName, password)) {
res.status(403).send({'message': 'You do not have access to the specified calendar.'});
return;
}
// Editors get the admin view (drafts included, calendar includes ignored);
// everyone else gets published events only.
let events: Event[] = editor
? await EventService.getAllEventsAdmin(calendarId)
: await EventService.getAllEvents(calendarId);
// Send the events back
res.status(200).send(events);
} catch (e: any) {
let errorGuid = Guid.create().toString();
logger.error('Error handling a request: ' + e.message, {reference: errorGuid});
res.status(500).send({'message': 'Internal Server Error. Try again later.'});
}
});
/**
* @swagger
* /calendar/events/{calendar}/json/next:
* get:
* summary: Get the next upcoming event from a calendar
* description: >
* The next upcoming event. The public calendar is open to everyone; the
* others need either a signed-in account with the calendar permission or the
* calendar's shared password.
* tags:
* - calendar
* parameters:
* - in: path
* name: calendar
* required: true
* schema:
* type: string
* enum: [public, members, choir, management, birthdays]
* description: The name of the calendar to get the next event from
* - in: query
* name: password
* schema:
* type: string
* description: The calendar's shared password, for callers with no account
* responses:
* 200:
* description: Success
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Event'
* 400:
* description: Bad request - missing or invalid parameters
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Please state the name of the calendar you want events from.
* 403:
* description: Forbidden - no access to the calendar
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: You do not have access to the specified calendar.
* 404:
* description: Not found - no upcoming events
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: No upcoming events found.
* 500:
* description: Server error
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: PROCESSING_ERROR
* message:
* type: string
* example: Internal Server Error. Try again later.
* reference:
* type: string
* example: 6ec1361c-4175-4e81-b2ef-a0792a9a1dc3
*/
eventsRouter.get('/:calendar/json/next', async (req: Request, res: Response) => {
try {
// Get request params
let calendarName: string = req.params.calendar as string ?? '';
let password: string = req.query.password as string ?? '';
if (calendarName.length < 1) {
res.status(400).send({'message': 'Please state the name of the calendar you want events from.'});
return;
}
if (!Array.from(calendarNames.keys()).includes(calendarName)) {
res.status(400).send({'message': 'Unknown calendar.'});
return;
}
let calendarId: number = calendarNames.get(calendarName)!.id;
// Holding the calendar's shared password, or signed in. The password path
// is what keeps iCal subscriptions working - a calendar client cannot
// send a cookie.
//
// The password is checked FIRST so that `public`, which needs no
// credential at all, short-circuits before signedInEditor runs. Otherwise
// every request from a browser that happens to hold a .nachklang.art
// cookie - which is any signed-in user on any of the four apps - would put
// an admin-database query in front of the anonymous public feed, with no
// timeout. Both operands are side-effect free, so the order is free to
// choose; this order is the one that keeps the public calendar
// independent of the admin database.
if (! await CredentialService.hasAccess(calendarName, password) && !await signedInEditor(req)) {
res.status(403).send({'message': 'You do not have access to the specified calendar.'});
return;
}
// Get next upcoming event
let event = await EventService.getNextUpcomingEvent(calendarId);
if (event === null) {
res.status(404).send({'message': 'No upcoming events found.'});
return;
}
// Send the event back
res.status(200).send(event);
} catch (e: any) {
let errorGuid = Guid.create().toString();
logger.error('Error handling a request: ' + e.message, {reference: errorGuid});
res.status(500).send({
'status': 'PROCESSING_ERROR',
'message': 'Internal Server Error. Try again later.',
'reference': errorGuid
});
}
});
/**
* @swagger
* /calendar/events/{calendar}/ical:
* get:
* summary: Get all events from a specific calendar in iCal format
* description: >
* The calendar in iCal format. The public calendar is open to everyone; the
* others take the calendar's shared password in the query string, which is
* why that mechanism survives - an iCal client cannot send a cookie.
* tags:
* - calendar
* parameters:
* - in: path
* name: calendar
* required: true
* schema:
* type: string
* enum: [public, members, choir, management, birthdays]
* description: The name of the calendar to get events from
* - in: query
* name: password
* schema:
* type: string
* description: The calendar's shared password, for callers with no account
* responses:
* 200:
* description: Success - returns iCal file
* content:
* text/calendar:
* schema:
* type: string
* format: binary
* 400:
* description: Bad request - missing or invalid parameters
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Please state the name of the calendar you want events from.
* 403:
* description: Forbidden - no access to the calendar
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: You do not have access to the specified calendar.
* 500:
* description: Server error
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: PROCESSING_ERROR
* message:
* type: string
* example: Internal Server Error. Try again later.
* reference:
* type: string
* example: 6ec1361c-4175-4e81-b2ef-a0792a9a1dc3
*/
eventsRouter.get('/:calendar/ical', async (req: Request, res: Response) => {
try {
// Get request params
let calendarName: string = req.params.calendar as string ?? '';
let password: string = req.query.password as string ?? '';
if (calendarName.length < 1) {
res.status(400).send({'message': 'Please state the name of the calendar you want events from.'});
return;
}
if (!Array.from(calendarNames.keys()).includes(calendarName)) {
res.status(400).send({'message': 'Unknown calendar.'});
return;
}
let calendarId: number = calendarNames.get(calendarName)!.id;
// Holding the calendar's shared password, or signed in. The password path
// is what keeps iCal subscriptions working - a calendar client cannot
// send a cookie.
//
// The password is checked FIRST so that `public`, which needs no
// credential at all, short-circuits before signedInEditor runs. Otherwise
// every request from a browser that happens to hold a .nachklang.art
// cookie - which is any signed-in user on any of the four apps - would put
// an admin-database query in front of the anonymous public feed, with no
// timeout. Both operands are side-effect free, so the order is free to
// choose; this order is the one that keeps the public calendar
// independent of the admin database.
if (! await CredentialService.hasAccess(calendarName, password) && !await signedInEditor(req)) {
res.status(403).send({'message': 'You do not have access to the specified calendar.'});
return;
}
// Get events
let events = await EventService.getAllEvents(calendarId);
// generate file name
let fileName = calendarNames.get(calendarName)!.name;
let file = await iCalService.convertToIcal(events);
// Send the ical file back
res.set({'Content-Disposition': 'attachment; filename=' + fileName + '.ics'});
res.status(200).send(file);
} catch (e: any) {
let errorGuid = Guid.create().toString();
logger.error('Error handling a request: ' + e.message, {reference: errorGuid});
res.status(500).send({
'status': 'PROCESSING_ERROR',
'message': 'Internal Server Error. Try again later.',
'reference': errorGuid
});
}
});
/**
* @swagger
* /calendar/events:
* post:
* summary: Create a new event
* description: Creates a new event. Requires a signed-in account with the calendar permission.
* tags:
* - calendar
* security:
* - AdminSessionCookie: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - calendarId
* - name
* - startDateTime
* - endDateTime
* properties:
* calendarId:
* type: integer
* example: 1
* name:
* type: string
* example: "Concert at Musikhochschule"
* description:
* type: string
* example: "Annual concert at the Musikhochschule"
* startDateTime:
* type: string
* format: date-time
* example: "2023-06-15T19:00:00.000Z"
* endDateTime:
* type: string
* format: date-time
* example: "2023-06-15T21:00:00.000Z"
* location:
* type: string
* example: "Musikhochschule, Karlsruhe"
* url:
* type: string
* example: "https://www.nachklang.art/events/concert"
* wholeDay:
* type: boolean
* example: false
* status:
* type: string
* enum: [PUBLIC, PRIVATE, DRAFT, DELETED]
* example: "PUBLIC"
* responses:
* 201:
* description: Event created successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Event with id 123 was created successfully.
* eventId:
* type: integer
* example: 123
* 400:
* description: Bad request - missing or invalid parameters
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Required parameters missing
* 401:
* description: Unauthorized - not signed in
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: UNAUTHORIZED
* message:
* type: string
* example: Anmeldung erforderlich.
* 403:
* description: Forbidden - the account lacks the calendar permission
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: FORBIDDEN
* message:
* type: string
* example: "Für diesen Bereich fehlt dir die Berechtigung."
* 500:
* description: Server error
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: PROCESSING_ERROR
* message:
* type: string
* example: Internal Server Error. Try again later.
* reference:
* type: string
* example: 6ec1361c-4175-4e81-b2ef-a0792a9a1dc3
*/
eventsRouter.post('/', requireCalendarAccess, async (req: Request, res: Response) => {
try {
const admin = adminOf(res);
if (
req.body.calendarId === undefined ||
isNullOrBlank(req.body.name) ||
req.body.startDateTime === undefined ||
req.body.endDateTime === undefined
) {
res.status(400).send({'message': 'Required parameters missing'});
return;
}
let event: Event = {
eventId: -1,
calendarId: req.body.calendarId,
uuid: '',
name: req.body.name,
description: req.body.description ?? '',
startDateTime: new Date(req.body.startDateTime),
endDateTime: new Date(req.body.endDateTime),
createdDate: new Date(),
location: req.body.location ?? '',
// LEGACY createdById is deliberately not set: there is no calendar
// user id any more, and migration 003 made the column nullable.
createdByUserId: admin.id,
url: req.body.url ?? '',
wholeDay: req.body.wholeDay ?? false,
repeatFrequency: req.body.repeatFrequency ?? '',
status: req.body.status ?? 'PUBLIC'
};
let eventId = await EventService.createEvent(event);
res.status(201).send({
'message': 'Event with id ' + eventId + ' was created successfully.',
'eventId': eventId
});
} catch (e: any) {
let errorGuid = Guid.create().toString();
logger.error('Error handling a request: ' + e.message, {reference: errorGuid});
res.status(500).send({
'status': 'PROCESSING_ERROR',
'message': 'Internal Server Error. Try again later.',
'reference': errorGuid
});
}
});
/**
* @swagger
* /calendar/events/{eventId}:
* put:
* summary: Update an existing event
* description: Updates an existing event. Requires a signed-in account with the calendar permission.
* tags:
* - calendar
* security:
* - AdminSessionCookie: []
* parameters:
* - in: path
* name: eventId
* required: true
* schema:
* type: integer
* description: The ID of the event to update
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - calendarId
* - name
* - startDateTime
* - endDateTime
* properties:
* calendarId:
* type: integer
* example: 1
* name:
* type: string
* example: "Updated Concert at Musikhochschule"
* description:
* type: string
* example: "Updated annual concert at the Musikhochschule"
* startDateTime:
* type: string
* format: date-time
* example: "2023-06-15T19:00:00.000Z"
* endDateTime:
* type: string
* format: date-time
* example: "2023-06-15T21:00:00.000Z"
* location:
* type: string
* example: "Musikhochschule, Karlsruhe"
* url:
* type: string
* example: "https://www.nachklang.art/events/concert"
* wholeDay:
* type: boolean
* example: false
* status:
* type: string
* enum: [PUBLIC, PRIVATE, DRAFT, DELETED]
* example: "PUBLIC"
* responses:
* 200:
* description: Event updated successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Event was successfully updated
* 400:
* description: Bad request - missing or invalid parameters
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Required parameters missing
* 401:
* description: Unauthorized - not signed in
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: UNAUTHORIZED
* message:
* type: string
* example: Anmeldung erforderlich.
* 403:
* description: Forbidden - the account lacks the calendar permission
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: FORBIDDEN
* message:
* type: string
* example: "Für diesen Bereich fehlt dir die Berechtigung."
* 500:
* description: Server error
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: PROCESSING_ERROR
* message:
* type: string
* example: Internal Server Error. Try again later.
* reference:
* type: string
* example: 6ec1361c-4175-4e81-b2ef-a0792a9a1dc3
*/
eventsRouter.put('/:eventId', requireCalendarAccess, async (req: Request, res: Response) => {
try {
const admin = adminOf(res);
if (
req.params.eventId === undefined ||
req.body.calendarId === undefined ||
isNullOrBlank(req.body.name) ||
req.body.startDateTime === undefined ||
req.body.endDateTime === undefined
) {
res.status(400).send({'message': 'Required parameters missing'});
return;
}
let event: Event = {
eventId: parseInt(req.params.eventId, 10),
calendarId: req.body.calendarId,
uuid: '',
name: req.body.name,
description: req.body.description ?? '',
startDateTime: new Date(req.body.startDateTime),
endDateTime: new Date(req.body.endDateTime),
createdDate: new Date(),
location: req.body.location ?? '',
// LEGACY createdById is deliberately not set: there is no calendar
// user id any more, and migration 003 made the column nullable.
createdByUserId: admin.id,
url: req.body.url ?? '',
wholeDay: req.body.wholeDay ?? false,
repeatFrequency: req.body.repeatFrequency ?? '',
status: req.body.status ?? 'PUBLIC'
};
let successRows = await EventService.updateEvent(event);
if (successRows === 1) {
res.status(200).send({'message': 'Event was successfully updated'});
} else if (successRows === 0) {
res.status(200).send({'message': '0 rows were updated'});
} else {
res.status(500).send({'message': 'An error occurred during the update process. Please try again.'});
}
} catch (e: any) {
let errorGuid = Guid.create().toString();
logger.error('Error handling a request: ' + e.message, {reference: errorGuid});
res.status(500).send({
'status': 'PROCESSING_ERROR',
'message': 'Internal Server Error. Try again later.',
'reference': errorGuid
});
}
});
/**
* @swagger
* /calendar/events/move/{eventId}:
* put:
* summary: Move an event to a different calendar
* description: Moves an event to a different calendar. Requires a signed-in account with the calendar permission.
* tags:
* - calendar
* security:
* - AdminSessionCookie: []
* parameters:
* - in: path
* name: eventId
* required: true
* schema:
* type: integer
* description: The ID of the event to move
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - calendarId
* properties:
* calendarId:
* type: integer
* example: 2
* description: The ID of the calendar to move the event to
* name:
* type: string
* example: "Concert at Musikhochschule"
* description:
* type: string
* example: "Annual concert at the Musikhochschule"
* startDateTime:
* type: string
* format: date-time
* example: "2023-06-15T19:00:00.000Z"
* endDateTime:
* type: string
* format: date-time
* example: "2023-06-15T21:00:00.000Z"
* location:
* type: string
* example: "Musikhochschule, Karlsruhe"
* url:
* type: string
* example: "https://www.nachklang.art/events/concert"
* wholeDay:
* type: boolean
* example: false
* status:
* type: string
* enum: [PUBLIC, PRIVATE, DRAFT, DELETED]
* example: "PUBLIC"
* responses:
* 200:
* description: Event moved successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Event was successfully moved
* 400:
* description: Bad request - missing or invalid parameters
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Required parameters missing
* 401:
* description: Unauthorized - not signed in
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: UNAUTHORIZED
* message:
* type: string
* example: Anmeldung erforderlich.
* 403:
* description: Forbidden - the account lacks the calendar permission
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: FORBIDDEN
* message:
* type: string
* example: "Für diesen Bereich fehlt dir die Berechtigung."
* 500:
* description: Server error
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: PROCESSING_ERROR
* message:
* type: string
* example: Internal Server Error. Try again later.
* reference:
* type: string
* example: 6ec1361c-4175-4e81-b2ef-a0792a9a1dc3
*/
eventsRouter.put('/move/:eventId', requireCalendarAccess, async (req: Request, res: Response) => {
try {
const admin = adminOf(res);
if (
req.params.eventId === undefined ||
req.body.calendarId === undefined
) {
res.status(400).send({'message': 'Required parameters missing'});
return;
}
let event: Event = {
eventId: parseInt(req.params.eventId, 10),
calendarId: req.body.calendarId,
uuid: '',
name: req.body.name,
description: req.body.description ?? '',
startDateTime: new Date(req.body.startDateTime),
endDateTime: new Date(req.body.endDateTime),
createdDate: new Date(),
location: req.body.location ?? '',
// LEGACY createdById is deliberately not set: there is no calendar
// user id any more, and migration 003 made the column nullable.
createdByUserId: admin.id,
url: req.body.url ?? '',
wholeDay: req.body.wholeDay ?? false,
repeatFrequency: req.body.repeatFrequency ?? '',
status: req.body.status ?? 'PUBLIC'
};
let success = await EventService.moveEvent(event);
if (success) {
res.status(200).send({'message': 'Event was successfully moved'});
} else {
res.status(500).send({'message': 'An error occurred during the moving process. Please try again.'});
}
} catch (e: any) {
let errorGuid = Guid.create().toString();
logger.error('Error handling a request: ' + e.message, {reference: errorGuid});
res.status(500).send({
'status': 'PROCESSING_ERROR',
'message': 'Internal Server Error. Try again later.',
'reference': errorGuid
});
}
});
/**
* @swagger
* /calendar/events/{eventId}:
* delete:
* summary: Delete an event
* description: Deletes an event. Requires a signed-in account with the calendar permission.
* tags:
* - calendar
* security:
* - AdminSessionCookie: []
* parameters:
* - in: path
* name: eventId
* required: true
* schema:
* type: integer
* description: The ID of the event to delete
* responses:
* 200:
* description: Event deleted successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Event was successfully deleted
* 400:
* description: Bad request - missing or invalid parameters
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Required parameters missing
* 401:
* description: Unauthorized - not signed in
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: UNAUTHORIZED
* message:
* type: string
* example: Anmeldung erforderlich.
* 403:
* description: Forbidden - the account lacks the calendar permission
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: FORBIDDEN
* message:
* type: string
* example: "Für diesen Bereich fehlt dir die Berechtigung."
* 500:
* description: Server error
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: PROCESSING_ERROR
* message:
* type: string
* example: Internal Server Error. Try again later.
* reference:
* type: string
* example: 6ec1361c-4175-4e81-b2ef-a0792a9a1dc3
*/
eventsRouter.delete('/:eventId', requireCalendarAccess, async (req: Request, res: Response) => {
try {
const admin = adminOf(res);
if (
req.params.eventId === undefined
) {
res.status(400).send({'message': 'Required parameters missing'});
return;
}
let event: Event = {
eventId: parseInt(req.params.eventId, 10),
calendarId: -1,
uuid: '',
name: '',
description: '',
startDateTime: new Date(),
endDateTime: new Date(),
createdDate: new Date(),
location: '',
createdBy: '',
// LEGACY createdById is deliberately not set: there is no calendar
// user id any more, and migration 003 made the column nullable.
createdByUserId: admin.id,
url: '',
wholeDay: false,
repeatFrequency: '',
status: 'DELETED'
};
let success = await EventService.deleteEvent(event);
if (success) {
res.status(200).send({'message': 'Event was successfully deleted'});
} else {
res.status(500).send({'message': 'An error occurred during deletion. Please try again.'});
}
} catch (e: any) {
let errorGuid = Guid.create().toString();
logger.error('Error handling a request: ' + e.message, {reference: errorGuid});
res.status(500).send({
'status': 'PROCESSING_ERROR',
'message': 'Internal Server Error. Try again later.',
'reference': errorGuid
});
}
});
/**
* Checks if a given string is null, undefined or blank
* @param str The string to check
*/
function isNullOrBlank(str: string | null): boolean {
return str === null || str === undefined || str.trim() === '';
}