This commit is contained in:
parent
a38fb20e5a
commit
45dfc22c60
1
app.ts
1
app.ts
|
@ -67,6 +67,7 @@ const options = {
|
|||
swaggerDefinition,
|
||||
// Paths to files containing OpenAPI definitions
|
||||
apis: [
|
||||
'./src/models/**/*.interface.ts',
|
||||
'./src/models/**/*.router.ts'
|
||||
]
|
||||
};
|
||||
|
|
|
@ -16,6 +16,39 @@ calendarRouter.use('/events', eventsRouter);
|
|||
calendarRouter.use('/users', usersRouter);
|
||||
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /calendar:
|
||||
* get:
|
||||
* summary: Calendar API root endpoint
|
||||
* description: Returns a welcome message for the Nachklang e.V. Calendar API.
|
||||
* tags:
|
||||
* - calendar
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
* content:
|
||||
* text/plain:
|
||||
* schema:
|
||||
* type: string
|
||||
* example: Nachklang e.V. Calendar API Endpoint
|
||||
* 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
|
||||
*/
|
||||
calendarRouter.get('/', async (req: Request, res: Response) => {
|
||||
try {
|
||||
res.status(200).send('Nachklang e.V. Calendar API Endpoint');
|
||||
|
|
|
@ -1,3 +1,84 @@
|
|||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* Event:
|
||||
* type: object
|
||||
* required:
|
||||
* - eventId
|
||||
* - calendarId
|
||||
* - uuid
|
||||
* - name
|
||||
* - description
|
||||
* - startDateTime
|
||||
* - endDateTime
|
||||
* - createdDate
|
||||
* - location
|
||||
* - createdById
|
||||
* - url
|
||||
* - wholeDay
|
||||
* properties:
|
||||
* eventId:
|
||||
* type: integer
|
||||
* description: The unique identifier for the event
|
||||
* example: 123
|
||||
* calendarId:
|
||||
* type: integer
|
||||
* description: The ID of the calendar this event belongs to
|
||||
* example: 1
|
||||
* uuid:
|
||||
* type: string
|
||||
* description: A unique UUID for the event
|
||||
* example: "550e8400-e29b-41d4-a716-446655440000"
|
||||
* name:
|
||||
* type: string
|
||||
* description: The name/title of the event
|
||||
* example: "Concert at Musikhochschule"
|
||||
* description:
|
||||
* type: string
|
||||
* description: A detailed description of the event
|
||||
* example: "Annual concert at the Musikhochschule"
|
||||
* startDateTime:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: The start date and time of the event
|
||||
* example: "2023-06-15T19:00:00.000Z"
|
||||
* endDateTime:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: The end date and time of the event
|
||||
* example: "2023-06-15T21:00:00.000Z"
|
||||
* createdDate:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: The date and time when the event was created
|
||||
* example: "2023-05-01T10:00:00.000Z"
|
||||
* location:
|
||||
* type: string
|
||||
* description: The location of the event
|
||||
* example: "Musikhochschule, Karlsruhe"
|
||||
* createdBy:
|
||||
* type: string
|
||||
* description: The name of the user who created the event
|
||||
* example: "John Doe"
|
||||
* createdById:
|
||||
* type: integer
|
||||
* description: The ID of the user who created the event
|
||||
* example: 456
|
||||
* url:
|
||||
* type: string
|
||||
* description: A URL with more information about the event
|
||||
* example: "https://www.nachklang.art/events/concert"
|
||||
* wholeDay:
|
||||
* type: boolean
|
||||
* description: Whether the event lasts the whole day
|
||||
* example: false
|
||||
* status:
|
||||
* type: string
|
||||
* description: The status of the event
|
||||
* enum: [PUBLIC, PRIVATE, DRAFT, DELETED]
|
||||
* example: "PUBLIC"
|
||||
*/
|
||||
export interface Event {
|
||||
eventId: number;
|
||||
calendarId: number;
|
||||
|
|
|
@ -33,6 +33,77 @@ export const calendarNames = new Map<string, any>([
|
|||
* Controller Definitions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /calendar/events/{calendar}/json:
|
||||
* get:
|
||||
* summary: Get all events from a specific calendar in JSON format
|
||||
* description: Returns all events from the specified calendar in JSON format. Authentication required.
|
||||
* tags:
|
||||
* - calendar
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: calendar
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [public, members, choir, management]
|
||||
* description: The name of the calendar to get events from
|
||||
* - in: query
|
||||
* name: sessionId
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session ID for authentication
|
||||
* - in: query
|
||||
* name: sessionKey
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session key for authentication
|
||||
* - in: query
|
||||
* name: password
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Password for calendar access (if not using session authentication)
|
||||
* 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
|
||||
|
@ -80,6 +151,214 @@ eventsRouter.get('/:calendar/json', async (req: Request, res: Response) => {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /calendar/events/{calendar}/json/next:
|
||||
* get:
|
||||
* summary: Get the next upcoming event from a calendar
|
||||
* description: Returns the next upcoming event from the specified calendar. Authentication required.
|
||||
* tags:
|
||||
* - calendar
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: calendar
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [public, members, choir, management]
|
||||
* description: The name of the calendar to get the next event from
|
||||
* - in: query
|
||||
* name: sessionId
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session ID for authentication
|
||||
* - in: query
|
||||
* name: sessionKey
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session key for authentication
|
||||
* - in: query
|
||||
* name: password
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Password for calendar access (if not using session authentication)
|
||||
* 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 sessionId: string = req.query.sessionId as string ?? '';
|
||||
let sessionKey: string = req.query.sessionKey as string ?? '';
|
||||
let password: string = req.query.password as string ?? '';
|
||||
let ip: string = req.socket.remoteAddress ?? '';
|
||||
|
||||
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;
|
||||
|
||||
if (! await CredentialService.hasAccess(calendarName, sessionId, sessionKey, password, ip)) {
|
||||
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: Returns all events from the specified calendar in iCal format for calendar applications. Authentication required.
|
||||
* tags:
|
||||
* - calendar
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: calendar
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [public, members, choir, management]
|
||||
* description: The name of the calendar to get events from
|
||||
* - in: query
|
||||
* name: sessionId
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session ID for authentication
|
||||
* - in: query
|
||||
* name: sessionKey
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session key for authentication
|
||||
* - in: query
|
||||
* name: password
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Password for calendar access (if not using session authentication)
|
||||
* 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
|
||||
|
@ -127,6 +406,120 @@ eventsRouter.get('/:calendar/ical', async (req: Request, res: Response) => {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /calendar/events:
|
||||
* post:
|
||||
* summary: Create a new event
|
||||
* description: Creates a new event in the specified calendar. Authentication required.
|
||||
* tags:
|
||||
* - calendar
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session ID for authentication
|
||||
* - in: query
|
||||
* name: sessionKey
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session key for authentication
|
||||
* 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
|
||||
* 403:
|
||||
* description: Forbidden - no access to create events
|
||||
* 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.post('/', async (req: Request, res: Response) => {
|
||||
try {
|
||||
// Get params
|
||||
|
@ -184,6 +577,126 @@ eventsRouter.post('/', async (req: Request, res: Response) => {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /calendar/events/{eventId}:
|
||||
* put:
|
||||
* summary: Update an existing event
|
||||
* description: Updates an existing event with the provided data. Authentication required.
|
||||
* tags:
|
||||
* - calendar
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: eventId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: The ID of the event to update
|
||||
* - in: query
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session ID for authentication
|
||||
* - in: query
|
||||
* name: sessionKey
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session key for authentication
|
||||
* 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"
|
||||
* createdBy:
|
||||
* type: string
|
||||
* example: "John Doe"
|
||||
* 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
|
||||
* 403:
|
||||
* description: Forbidden - no access to update events
|
||||
* 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.put('/:eventId', async (req: Request, res: Response) => {
|
||||
try {
|
||||
// Get params
|
||||
|
@ -246,6 +759,124 @@ eventsRouter.put('/:eventId', async (req: Request, res: Response) => {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /calendar/events/move/{eventId}:
|
||||
* put:
|
||||
* summary: Move an event to a different calendar
|
||||
* description: Moves an existing event to a different calendar. Authentication required.
|
||||
* tags:
|
||||
* - calendar
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: eventId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: The ID of the event to move
|
||||
* - in: query
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session ID for authentication
|
||||
* - in: query
|
||||
* name: sessionKey
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session key for authentication
|
||||
* 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"
|
||||
* createdBy:
|
||||
* type: string
|
||||
* example: "John Doe"
|
||||
* 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
|
||||
* 403:
|
||||
* description: Forbidden - no access to move events
|
||||
* 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.put('/move/:eventId', async (req: Request, res: Response) => {
|
||||
try {
|
||||
// Get params
|
||||
|
@ -303,6 +934,81 @@ eventsRouter.put('/move/:eventId', async (req: Request, res: Response) => {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /calendar/events/{eventId}:
|
||||
* delete:
|
||||
* summary: Delete an event
|
||||
* description: Deletes an existing event. Authentication required.
|
||||
* tags:
|
||||
* - calendar
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: eventId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: The ID of the event to delete
|
||||
* - in: query
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session ID for authentication
|
||||
* - in: query
|
||||
* name: sessionKey
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session key for authentication
|
||||
* 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
|
||||
* 403:
|
||||
* description: Forbidden - no access to delete events
|
||||
* 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.delete('/:eventId', async (req: Request, res: Response) => {
|
||||
try {
|
||||
// Get params
|
||||
|
|
|
@ -211,3 +211,63 @@ export const moveEvent = async (event: Event): Promise<boolean> => {
|
|||
await conn.end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next upcoming event for the given calendar
|
||||
* @param calendarId The calendar Id
|
||||
*/
|
||||
export const getNextUpcomingEvent = async (calendarId: number): Promise<Event | null> => {
|
||||
let conn = await NachklangCalendarDB.getConnection();
|
||||
try {
|
||||
const calendarQuery = 'SELECT calendar_id, includes_calendars FROM calendars WHERE calendar_id = ?';
|
||||
const calendarRes = await conn.query(calendarQuery, calendarId);
|
||||
let calendarsToFetch: number[] = [calendarId];
|
||||
for(let row of calendarRes) {
|
||||
let includes: number[] = JSON.parse(row.includes_calendars);
|
||||
calendarsToFetch = [...calendarsToFetch, ...includes];
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const eventsQuery = `
|
||||
SELECT e.calendar_id, e.uuid, e.created_date, e.created_by_id, u.full_name, v.* FROM events e
|
||||
INNER JOIN (
|
||||
SELECT event_id, MAX(event_version_id) AS latest_version
|
||||
FROM event_versions
|
||||
GROUP BY event_id
|
||||
) latest_versions
|
||||
ON e.event_id = latest_versions.event_id
|
||||
INNER JOIN event_versions v
|
||||
ON v.event_id = latest_versions.event_id AND v.event_version_id = latest_versions.latest_version
|
||||
LEFT OUTER JOIN users u ON u.user_id = e.created_by_id
|
||||
WHERE e.calendar_id IN (?) AND v.status = 'PUBLIC' AND v.start_datetime > ?
|
||||
ORDER BY v.start_datetime ASC
|
||||
LIMIT 1`;
|
||||
const eventsRes = await conn.query(eventsQuery, [calendarsToFetch, now]);
|
||||
|
||||
if (eventsRes.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const row = eventsRes[0];
|
||||
return {
|
||||
eventId: row.event_id,
|
||||
calendarId: row.calendar_id,
|
||||
uuid: row.uuid,
|
||||
name: row.name,
|
||||
description: row.description,
|
||||
startDateTime: row.start_datetime,
|
||||
endDateTime: row.end_datetime,
|
||||
createdDate: row.created_date,
|
||||
location: row.location,
|
||||
createdBy: row.full_name,
|
||||
createdById: row.created_by_id,
|
||||
url: row.url,
|
||||
wholeDay: row.whole_day
|
||||
} as Event;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
} finally {
|
||||
// Return connection
|
||||
await conn.end();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,47 @@
|
|||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* Session:
|
||||
* type: object
|
||||
* required:
|
||||
* - sessionId
|
||||
* - userId
|
||||
* - sessionKey
|
||||
* - sessionKeyHash
|
||||
* - lastIP
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: integer
|
||||
* description: The unique identifier for the session
|
||||
* example: 789
|
||||
* userId:
|
||||
* type: integer
|
||||
* description: The ID of the user this session belongs to
|
||||
* example: 456
|
||||
* sessionKey:
|
||||
* type: string
|
||||
* description: The session key used for authentication
|
||||
* example: "abc123def456"
|
||||
* sessionKeyHash:
|
||||
* type: string
|
||||
* description: The hashed session key (not returned in API responses)
|
||||
* example: "$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG"
|
||||
* createdDate:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: The date and time when the session was created
|
||||
* example: "2023-05-01T10:00:00.000Z"
|
||||
* validUntil:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: The date and time until when the session is valid
|
||||
* example: "2023-05-08T10:00:00.000Z"
|
||||
* lastIP:
|
||||
* type: string
|
||||
* description: The last IP address used with this session
|
||||
* example: "192.168.1.1"
|
||||
*/
|
||||
export interface Session {
|
||||
sessionId: number;
|
||||
userId: number;
|
||||
|
|
|
@ -1,3 +1,38 @@
|
|||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* User:
|
||||
* type: object
|
||||
* required:
|
||||
* - userId
|
||||
* - fullName
|
||||
* - passwordHash
|
||||
* - email
|
||||
* - isActive
|
||||
* properties:
|
||||
* userId:
|
||||
* type: integer
|
||||
* description: The unique identifier for the user
|
||||
* example: 456
|
||||
* fullName:
|
||||
* type: string
|
||||
* description: The full name of the user
|
||||
* example: "John Doe"
|
||||
* passwordHash:
|
||||
* type: string
|
||||
* description: The hashed password of the user (not returned in API responses)
|
||||
* example: "$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG"
|
||||
* email:
|
||||
* type: string
|
||||
* format: email
|
||||
* description: The email address of the user
|
||||
* example: "john.doe@nachklang.art"
|
||||
* isActive:
|
||||
* type: boolean
|
||||
* description: Whether the user account is active
|
||||
* example: true
|
||||
*/
|
||||
export interface User {
|
||||
userId: number;
|
||||
fullName: string;
|
||||
|
|
|
@ -20,6 +20,78 @@ export const usersRouter = express.Router();
|
|||
* Controller Definitions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /calendar/users/register:
|
||||
* post:
|
||||
* summary: Register a new user
|
||||
* description: Creates a new user account with the provided email, password, and full name. Only accepts official Nachklang email addresses.
|
||||
* tags:
|
||||
* - calendar
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - email
|
||||
* - password
|
||||
* - fullName
|
||||
* properties:
|
||||
* email:
|
||||
* type: string
|
||||
* format: email
|
||||
* example: john.doe@nachklang.art
|
||||
* description: Must be an official Nachklang email address
|
||||
* password:
|
||||
* type: string
|
||||
* format: password
|
||||
* example: securePassword123
|
||||
* fullName:
|
||||
* type: string
|
||||
* example: John Doe
|
||||
* responses:
|
||||
* 201:
|
||||
* description: User registered successfully
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: integer
|
||||
* example: 123
|
||||
* sessionKey:
|
||||
* type: string
|
||||
* example: abc123def456
|
||||
* 400:
|
||||
* description: Bad request - missing or invalid parameters
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* message:
|
||||
* type: string
|
||||
* example: Missing parameters
|
||||
* 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
|
||||
*/
|
||||
// POST users/register
|
||||
usersRouter.post('/register', async (req: Request, res: Response) => {
|
||||
try {
|
||||
|
@ -60,6 +132,71 @@ usersRouter.post('/register', async (req: Request, res: Response) => {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /calendar/users/activate:
|
||||
* get:
|
||||
* summary: Activate a user account
|
||||
* description: Activates a user account using the provided user ID and activation token.
|
||||
* tags:
|
||||
* - calendar
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: The ID of the user to activate
|
||||
* - in: query
|
||||
* name: token
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: The activation token sent to the user's email
|
||||
* responses:
|
||||
* 200:
|
||||
* description: User activated successfully
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* status:
|
||||
* type: string
|
||||
* example: OK
|
||||
* message:
|
||||
* type: string
|
||||
* example: User activated
|
||||
* 400:
|
||||
* description: Bad request - missing parameters or activation failed
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* status:
|
||||
* type: string
|
||||
* example: PROCESSING_ERROR
|
||||
* message:
|
||||
* type: string
|
||||
* example: Error activating user. Please contact your administrator.
|
||||
* 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
|
||||
*/
|
||||
// GET /users/activate
|
||||
usersRouter.get('/activate', async (req: Request, res: Response) => {
|
||||
try {
|
||||
|
@ -95,6 +232,89 @@ usersRouter.get('/activate', async (req: Request, res: Response) => {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /calendar/users/login:
|
||||
* post:
|
||||
* summary: Login a user
|
||||
* description: Authenticates a user with the provided email and password and returns a session.
|
||||
* tags:
|
||||
* - calendar
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - email
|
||||
* - password
|
||||
* properties:
|
||||
* email:
|
||||
* type: string
|
||||
* format: email
|
||||
* example: john.doe@nachklang.art
|
||||
* password:
|
||||
* type: string
|
||||
* format: password
|
||||
* example: securePassword123
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Login successful
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: integer
|
||||
* example: 123
|
||||
* sessionKey:
|
||||
* type: string
|
||||
* example: abc123def456
|
||||
* 400:
|
||||
* description: Bad request - missing parameters
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* message:
|
||||
* type: string
|
||||
* example: Missing parameters
|
||||
* 401:
|
||||
* description: Unauthorized - invalid credentials
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* message:
|
||||
* type: string
|
||||
* example: Wrong username and / or password
|
||||
* sessionId:
|
||||
* type: integer
|
||||
* example: -1
|
||||
* sessionKey:
|
||||
* type: string
|
||||
* example: ""
|
||||
* 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
|
||||
*/
|
||||
// POST users/login
|
||||
usersRouter.post('/login', async (req: Request, res: Response) => {
|
||||
try {
|
||||
|
@ -133,6 +353,66 @@ usersRouter.post('/login', async (req: Request, res: Response) => {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /calendar/users/checkSessionValid:
|
||||
* post:
|
||||
* summary: Check if a session is valid
|
||||
* description: Checks if the provided session is valid and returns the user information if it is.
|
||||
* tags:
|
||||
* - calendar
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - sessionId
|
||||
* - sessionKey
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: integer
|
||||
* example: 123
|
||||
* sessionKey:
|
||||
* type: string
|
||||
* example: abc123def456
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Session is valid
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/User'
|
||||
* 401:
|
||||
* description: Unauthorized - invalid session
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* messages:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* example: ["Invalid session"]
|
||||
* 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
|
||||
*/
|
||||
// POST users/checkSessionValid
|
||||
usersRouter.post('/checkSessionValid', async (req: Request, res: Response) => {
|
||||
try {
|
||||
|
|
Loading…
Reference in New Issue
Block a user