Skip to content

Commit

Permalink
Merge pull request #9 from Xitija/main
Browse files Browse the repository at this point in the history
PS-1371 and PS-1387
  • Loading branch information
vaivk369 authored Jul 16, 2024
2 parents bcb05e4 + 7a89448 commit 8e4f60d
Show file tree
Hide file tree
Showing 17 changed files with 695 additions and 661 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/supertest": "^2.0.12",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"eslint": "^8.42.0",
Expand Down
8 changes: 6 additions & 2 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ import { EventModule } from './modules/event/event.module';
import { DatabaseModule } from './common/database-modules';
import { AttendeesModule } from './modules/attendees/attendees.module';


@Module({
imports: [ConfigModule.forRoot({ isGlobal: true }), EventModule, DatabaseModule, AttendeesModule],
imports: [
ConfigModule.forRoot({ isGlobal: true }),
EventModule,
DatabaseModule,
AttendeesModule,
],
controllers: [AppController],
providers: [AppService],
})
Expand Down
29 changes: 29 additions & 0 deletions src/common/filters/exception.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Response, Request } from 'express';
import APIResponse from '../utils/response';
import { ERROR_MESSAGES } from '../utils/constants.util';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
constructor(private readonly apiId?: string) { }

catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const status = exception instanceof HttpException ? exception.getStatus() : 500;
const exceptionResponse = exception instanceof HttpException ? exception.getResponse() : null;
const errorMessage =
exception instanceof HttpException
? (exceptionResponse as any).message || exception.message
: ERROR_MESSAGES.INTERNAL_SERVER_ERROR;
const detailedErrorMessage = `${errorMessage}`;
console.log('detailedErrorMessage', detailedErrorMessage);
const errorResponse = APIResponse.error(
this.apiId,
detailedErrorMessage,
exception instanceof HttpException ? exception.name : ERROR_MESSAGES.INTERNAL_SERVER_ERROR, // error
status.toString(),
);
return response.status(status).json(errorResponse);
}
}
251 changes: 193 additions & 58 deletions src/common/pipes/event-validation.pipe.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,208 @@
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import {
PipeTransform,
Injectable,
BadRequestException,
forwardRef,
Inject,
} from '@nestjs/common';
import { CreateEventDto } from 'src/modules/event/dto/create-event.dto';

import { getTimezoneDate } from '../utils/pipe.util';
import { get } from 'http';
import { ERROR_MESSAGES } from '../utils/constants.util';
import {
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments,
} from 'class-validator';
@Injectable()
export class DateValidationPipe implements PipeTransform {
transform(createEventDto: CreateEventDto) {
const startDate = new Date(createEventDto.startDatetime);
const endDate = new Date(createEventDto.endDatetime);
const currentDate = new Date(); // Current date
// Check if start date is today or in the future
if (startDate < currentDate) {
throw new BadRequestException('Start date must be today or a future date');
}
if (endDate < startDate) {
throw new BadRequestException('End date should be greater than or equal to start date');
}
return createEventDto;
// constructor(@Inject(forwardRef(() => ConfigService)) private configService: ConfigService) {

// }

transform(createEventDto: CreateEventDto) {
const timeZone = 'Asia/Kolkata';
const startDate = getTimezoneDate(
timeZone,
new Date(createEventDto.startDatetime),
);
const endDate = getTimezoneDate(
timeZone,
new Date(createEventDto.endDatetime),
);
const currentDate = getTimezoneDate(timeZone); // Current date
// this.configService.get<string>('TIMEZONE'); // Get the timezone from the config service

console.log('currentDate', currentDate);
console.log('startDate', startDate);
console.log('endDate', endDate);

console.log(startDate <= currentDate, 'startDate <= currentDate');

if (startDate <= currentDate) {
throw new BadRequestException(
'Start date must be today or a future date',
);
}
if (endDate < startDate) {
throw new BadRequestException(
'End date should be greater than or equal to start date',
);
}
return createEventDto;
}
}


@Injectable()
export class DeadlineValidationPipe implements PipeTransform {
transform(createEventDto: CreateEventDto) {
const startDate = new Date(createEventDto.startDatetime);
const endDate = new Date(createEventDto.endDatetime);
const registrationDeadline = new Date(createEventDto.registrationDeadline);

if (registrationDeadline < startDate || registrationDeadline > endDate) {
throw new BadRequestException('Registration deadline should be between start date and end date');
}
return createEventDto;
export class RegistrationDateValidationPipe implements PipeTransform {
transform(createEventDto: CreateEventDto) {
const timeZone = 'Asia/Kolkata';
const currentDate = getTimezoneDate(timeZone);
const startDate = getTimezoneDate(
timeZone,
new Date(createEventDto.startDatetime),
);
const endDate = getTimezoneDate(
timeZone,
new Date(createEventDto.endDatetime),
);
const registrationStartDate = createEventDto.registrationEndDate
? getTimezoneDate(
timeZone,
new Date(createEventDto.registrationStartDate),
)
: null;
const isRestricted = createEventDto.isRestricted;
const registrationEndDate = createEventDto.registrationEndDate
? getTimezoneDate(timeZone, new Date(createEventDto.registrationEndDate))
: null;

console.log(
registrationStartDate,
'rrrrr',
startDate,
registrationStartDate > startDate,
registrationStartDate < startDate,
);
console.log(
createEventDto.isRestricted && registrationStartDate,
'createEventDto.isRestricted && registrationStartDate ',
);
console.log(
createEventDto.isRestricted && registrationEndDate,
'createEventDto.isRestricted && registrationEndDate',
);
if (
(createEventDto.isRestricted && registrationStartDate) ||
(createEventDto.isRestricted && registrationEndDate)
) {
console.log('');
throw new BadRequestException(
ERROR_MESSAGES.RESTRICTED_EVENT_NO_REGISTRATION_DATE,
);
}

// Ensure registration dates are not in the past
if (registrationStartDate < currentDate && !isRestricted) {
throw new BadRequestException(
ERROR_MESSAGES.REGISTRATION_START_DATE_INVALID,
);
}

if (registrationEndDate < currentDate && !isRestricted) {
throw new BadRequestException(
ERROR_MESSAGES.REGISTRATION_END_DATE_INVALID,
);
}

// Validate registration dates
if (registrationStartDate > registrationEndDate && !isRestricted) {
throw new BadRequestException(
ERROR_MESSAGES.REGISTRATION_START_DATE_BEFORE_END_DATE,
);
}

// Registration period must fall between the event period
if (registrationStartDate > startDate && !isRestricted) {
throw new BadRequestException(
ERROR_MESSAGES.REGISTRATION_START_DATE_BEFORE_EVENT_DATE,
);
}

if (registrationEndDate > startDate && !isRestricted) {
throw new BadRequestException(
ERROR_MESSAGES.REGISTRATION_END_DATE_BEFORE_EVENT_DATE,
);
}

return createEventDto;
}
}

export class RecurringEndDateValidationPipe implements PipeTransform {
transform(createEventDto: CreateEventDto) {
const currentDate = getTimezoneDate('Asia/Kolkata');
if (createEventDto.isRecurring) {
const recurrenceEndDate = new Date(createEventDto.recurrenceEndDate);
const startDate = new Date(createEventDto.startDatetime);

if (recurrenceEndDate < currentDate) {
throw new BadRequestException(
ERROR_MESSAGES.RECURRENCE_END_DATE_INVALID,
);
}

if (recurrenceEndDate < startDate) {
throw new BadRequestException(
ERROR_MESSAGES.RECURRENCE_END_DATE_BEFORE_EVENT_DATE,
);
}
}

return createEventDto;
}
}

@Injectable()
export class ParamsValidationPipe implements PipeTransform {
transform(createEventDto: CreateEventDto) {
if (createEventDto.isRestricted) {
const params = createEventDto.params;
if (!params || typeof params !== 'object') {
throw new BadRequestException('Invalid params object');
}

if (!params.cohortIds && !params.userIds) {
throw new BadRequestException('Either cohortIds or userIds must be provided in params');
}

if (params.cohortIds && params.userIds) {
throw new BadRequestException('Only one of cohortIds or userIds should be provided in params');
}

if (params.cohortIds) {
this.validateUUIDs(params.cohortIds);
} else if (params.userIds) {
this.validateUUIDs(params.userIds);
}
} else if (!createEventDto.isRestricted) {
createEventDto.params = {};
}

return createEventDto;
transform(createEventDto: CreateEventDto) {
if (createEventDto.isRestricted) {
const params = createEventDto.params;
if (!params || typeof params !== 'object') {
throw new BadRequestException('Invalid params object');
}

// if (!params.cohortIds && !params.userIds) {
// throw new BadRequestException(
// 'Either cohortIds or userIds must be provided in params',
// );
// }

// if (params.cohortIds && params.userIds) {
// throw new BadRequestException(
// 'Only one of cohortIds or userIds should be provided in params',
// );
// }

// if (params.cohortIds) {
// this.validateUUIDs(params.cohortIds);
// } else if (params.userIds) {
// this.validateUUIDs(params.userIds);
// }
} else if (!createEventDto.isRestricted) {
createEventDto.params = {};
}

private validateUUIDs(ids: string[]) {
const uuidRegex = /^[a-f\d]{8}(-[a-f\d]{4}){3}-[a-f\d]{12}$/i; // UUID regex pattern
for (const id of ids) {
if (!uuidRegex.test(id)) {
throw new BadRequestException(`Invalid UUID format: ${id}`);
}
}
return createEventDto;
}

private validateUUIDs(ids: string[]) {
const uuidRegex = /^[a-f\d]{8}(-[a-f\d]{4}){3}-[a-f\d]{12}$/i; // UUID regex pattern
for (const id of ids) {
if (!uuidRegex.test(id)) {
throw new BadRequestException(`Invalid UUID format: ${id}`);
}
}
}
}
}
56 changes: 56 additions & 0 deletions src/common/utils/constants.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
export const ERROR_MESSAGES = {
INVALID_REQUEST: 'Invalid request',
NOT_FOUND: 'Not found',
UNAUTHORIZED: 'Unauthorized',
FORBIDDEN: 'Forbidden',
BAD_REQUEST: 'Bad request',
INVALID_REQUEST_BODY: 'Invalid request body',
INTERNAL_SERVER_ERROR: 'Internal Server Error',
REGISTRATION_DATE_INVALID: 'Registration date must be in the future',
REGISTRATION_START_DATE_BEFORE_EVENT_DATE: 'Registration start date must be before the event start date',
REGISTRATION_END_DATE_BEFORE_EVENT_DATE: 'Registration end date must be on or before the event start date',
REGISTRATION_START_DATE_INVALID: 'Registration start date must be in the future',
REGISTRATION_END_DATE_INVALID: 'Registration end date must be in the future',
REGISTRATION_START_DATE_BEFORE_END_DATE: 'Registration start date must be before registration end date',
RECURRENCE_END_DATE_INVALID: 'Recurrence end date must be in the future',
RECURRENCE_END_DATE_BEFORE_EVENT_DATE: 'Recurrence end date must be after the event start date',
RECURRING_PATTERN_REQUIRED: 'Recurrence Pattern required for event',
REGISTRATION_START_DATE_REQUIRED: 'Registration Start Date required for event',
INVITEES_REQUIRED: 'Invitees required for private event',
INVITEES_NOT_REQUIRED: 'Invitees not required for public event',
EVENT_NOT_FOUND: 'Event not found',
EVENT_ATTENDEE_NOT_FOUND: 'Event attendee not found',
EVENT_ATTENDEE_HISTORY_NOT_FOUND: 'Event attendee history not found',
EVENT_ATTENDEE_HISTORY_ITEM_NOT_FOUND: 'Event attendee history item not found',
}

export const SUCCESS_MESSAGES = {
EVENT_CREATED: 'Event created successfully',
EVENT_UPDATED: 'Event updated successfully',
EVENT_DELETED: 'Event deleted successfully',
EVENT_NOT_FOUND: 'Event not found',
EVENT_ATTENDEE_CREATED: 'Event attendee created successfully',
EVENT_ATTENDEE_UPDATED: 'Event attendee updated successfully',
EVENT_ATTENDEE_DELETED: 'Event attendee deleted successfully',
EVENT_ATTENDEE_HISTORY_ITEM_CREATED: 'Event attendee history item created successfully',
EVENT_ATTENDEE_HISTORY_ITEM_UPDATED: 'Event attendee history item updated successfully',
EVENT_ATTENDEE_HISTORY_ITEM_DELETED: 'Event attendee history item deleted successfully',
}

export const API_ID = {
CREATE_EVENT: 'api.event.create',
GET_EVENT_BY_ID: 'api.event.getbyid',
GET_EVENTS: 'api.events.get',
UPDATE_EVENT: 'api.event.update',
DELETE_EVENT: 'api.event.delete',
GET_EVENT_ATTENDEES: 'api.event.attendees.get',
GET_EVENT_ATTENDEE: 'api.event.attendee.get',
CREATE_EVENT_ATTENDEE: 'api.event.attendee.create',
UPDATE_EVENT_ATTENDEE: 'api.event.attendee.update',
DELETE_EVENT_ATTENDEE: 'api.event.attendee.delete',
GET_EVENT_ATTENDEE_HISTORY: 'api.event.attendee.history.get',
GET_EVENT_ATTENDEE_HISTORY_ITEM: 'api.event.attendee.history.item.get',
CREATE_EVENT_ATTENDEE_HISTORY_ITEM: 'api.event.attendee.history.item.create',
UPDATE_EVENT_ATTENDEE_HISTORY_ITEM: 'api.event.attendee.history.item.update',
DELETE_EVENT_ATTENDEE_HISTORY_ITEM: 'api.event.attendee.history.item.delete',
}
10 changes: 10 additions & 0 deletions src/common/utils/pipe.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function getTimezoneDate(timeZone: string, nowUtc: Date = new Date()) {
// converts the current date(UTC) to the timezone -> 2024-07-15T11:07:09.827Z => 2024-07-15T16:37:09.827Z
const nowTimezone = new Date(
nowUtc.toLocaleString('en-US', { timeZone: timeZone }),
);

const offset = nowTimezone.getTimezoneOffset() * 60000;

return new Date(nowTimezone.getTime() - offset); //.toISOString() //.slice(0, -1);
}
Loading

0 comments on commit 8e4f60d

Please sign in to comment.