diff --git a/src/common/utils/validation.util.ts b/src/common/utils/validation.util.ts new file mode 100644 index 0000000..9beb877 --- /dev/null +++ b/src/common/utils/validation.util.ts @@ -0,0 +1,41 @@ +import { + ValidatorConstraint, + ValidatorConstraintInterface, + ValidationArguments, +} from 'class-validator'; + +function validateMeetingUrl(url, provider) { + const providerPatterns = { + zoom: /^https:\/\/[\w-]*\.?zoom.us\/(j|my)\/[\d\w?=-]+$/, + googlemeet: /^(https:\/\/)?meet\.google\.com\/[a-zA-Z0-9-]+$/, + // microsoftteams: /^(https:\/\/)?teams\.microsoft\.com\/[a-zA-Z0-9?&=]+$/, + // Add other supported providers as needed + }; + if ( + !url || + typeof url !== 'string' || + !provider || + typeof provider !== 'string' + ) { + return false; + } + + const pattern = providerPatterns[provider.toLowerCase()]; + if (!pattern) { + return false; // Unsupported provider + } + + return pattern.test(url); +} + +@ValidatorConstraint({ name: 'urlWithProviderValidator', async: false }) +export class UrlWithProviderValidator implements ValidatorConstraintInterface { + validate(url: string, args: ValidationArguments) { + const { onlineProvider } = args.object as any; + return validateMeetingUrl(url, onlineProvider); + } + + defaultMessage(args: ValidationArguments) { + return 'Invalid meeting URL for the specified provider!'; + } +} diff --git a/src/modules/event/dto/create-event.dto.ts b/src/modules/event/dto/create-event.dto.ts index f14a4ea..ebd25dc 100644 --- a/src/modules/event/dto/create-event.dto.ts +++ b/src/modules/event/dto/create-event.dto.ts @@ -21,7 +21,7 @@ import { IsIn, } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; +import { Transform, Type } from 'class-transformer'; import { EndConditionType, EventTypes, @@ -31,8 +31,12 @@ import { } from 'src/common/utils/types'; import { ERROR_MESSAGES } from 'src/common/utils/constants.util'; import { EndsWithZConstraint } from 'src/common/pipes/event-validation.pipe'; +import { UrlWithProviderValidator } from 'src/common/utils/validation.util'; export class MeetingDetailsDto { + // Pass the provider from the parent DTO + onlineProvider: string; + @ApiProperty({ description: 'Meeting ID', example: 94292617 }) @IsString() @IsNotEmpty() @@ -44,7 +48,7 @@ export class MeetingDetailsDto { }) @IsString() @IsNotEmpty() - // @Validate(UrlValidator) + @Validate(UrlWithProviderValidator) url: string; @ApiProperty({ description: 'Meeting password', example: 'xxxxxx' }) @@ -253,7 +257,7 @@ export class CreateEventDto { @ValidateIf((o) => o.eventType === EventTypes.online) @IsString() @IsNotEmpty() - @IsIn(['Zoom', 'GoogleMeet', 'MicrosoftTeams']) // Supported providers + @IsIn(['Zoom', 'GoogleMeet']) //, 'MicrosoftTeams' // Supported providers onlineProvider: string; @ApiProperty({ @@ -279,8 +283,11 @@ export class CreateEventDto { @ValidateIf((o) => o.eventType === 'online') @ValidateNested({ each: true }) @Type(() => MeetingDetailsDto) + @Transform(({ value, obj }) => { + value.onlineProvider = obj.onlineProvider; // Pass the provider to the nested DTO + return value; + }) meetingDetails: MeetingDetails; - // TODO: meet url validation @ApiProperty({ type: Number, diff --git a/src/modules/event/dto/search-event.dto.ts b/src/modules/event/dto/search-event.dto.ts index c92e961..5c2edef 100644 --- a/src/modules/event/dto/search-event.dto.ts +++ b/src/modules/event/dto/search-event.dto.ts @@ -64,7 +64,7 @@ export class FilterDto { @ApiProperty({ example: 'CohortId', description: 'CohortId' }) @IsOptional() @IsUUID('4') - cohortId?: string + cohortId?: string; } export class SearchFilterDto { diff --git a/src/modules/event/event.service.ts b/src/modules/event/event.service.ts index dc151a8..bd9cabb 100644 --- a/src/modules/event/event.service.ts +++ b/src/modules/event/event.service.ts @@ -124,8 +124,7 @@ export class EventService { // Append LIMIT and OFFSET to the query finalquery += ` LIMIT ${limit} OFFSET ${offset}`; const result = await this.eventRepetitionRepository.query(finalquery); - const totalCount = result[0]?.total_count - + const totalCount = result[0]?.total_count; // Add isEnded key based on endDateTime const finalResult = result.map((event) => { @@ -137,7 +136,7 @@ export class EventService { }; }); if (finalResult.length === 0) { - throw new NotFoundException('Event Not Found') + throw new NotFoundException('Event Not Found'); } return response .status(HttpStatus.OK) @@ -149,7 +148,7 @@ export class EventService { ), ); } catch (error) { - throw error + throw error; } } @@ -220,7 +219,7 @@ export class EventService { // Handle cohortId filter if (filters.cohortId) { - whereClauses.push(`ed."metadata"->>'cohortId'='${filters.cohortId}'`) + whereClauses.push(`ed."metadata"->>'cohortId'='${filters.cohortId}'`); } // Construct final query @@ -539,7 +538,7 @@ export class EventService { if ( config.endCondition.type === 'endDate' && occurrences[occurrences.length - 1]?.endDateTime > - new Date(config.endCondition.value) + new Date(config.endCondition.value) ) { occurrences.pop(); }