Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PS: 3353 Add generic typeorm service #55

Merged
merged 22 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ import { AppController } from './app.controller';
import { AppService } from './app.service';
import { EventModule } from './modules/event/event.module';
import { DatabaseModule } from './common/database-modules';
import { AttendeesModule } from './modules/attendees/attendees.module';
import { AttendanceModule } from './modules/attendance/attendance.module';

@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
EventModule,
DatabaseModule,
AttendeesModule,
AttendanceModule,
],
controllers: [AppController],
Expand Down
37 changes: 37 additions & 0 deletions src/common/config/routeConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module.exports = {
routes: [
{
sourceRoute: '/event-service/event/v1/create',
type: 'POST',
inSequence: true,
orchestrated: true,
targetRoute: {
path: '/event-service/event/v1/create',
type: 'POST',
functionName: 'createEvent',
},
},
{
sourceRoute: '/event-service/event/v1/:id',
type: 'PATCH',
inSequence: true,
orchestrated: true,
targetRoute: {
path: '/event-service/event/v1/:id',
type: 'PATCH',
functionName: 'updateEvent',
},
},
{
sourceRoute: '/event-service/event/v1/list',
type: 'POST',
inSequence: true,
orchestrated: true,
targetRoute: {
path: '/event-service/event/v1/list',
type: 'POST',
functionName: 'listEvents',
},
},
],
};
4 changes: 2 additions & 2 deletions src/common/pipes/event-validation.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import {
BadRequestException,
ArgumentMetadata,
} from '@nestjs/common';
import { CreateEventDto } from 'src/modules/event/dto/create-event.dto';
import { CreateEventDto } from '../../modules/event/dto/create-event.dto';
import { ERROR_MESSAGES } from '../utils/constants.util';
import {
ValidationArguments,
ValidatorConstraint,
ValidatorConstraintInterface,
} from 'class-validator';
import { EndConditionType } from '../utils/types';
import { UpdateEventDto } from 'src/modules/event/dto/update-event.dto';
import { UpdateEventDto } from '../../modules/event/dto/update-event.dto';

@Injectable()
export class DateValidationPipe implements PipeTransform {
Expand Down
101 changes: 101 additions & 0 deletions src/common/services/typeorm.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { Injectable } from '@nestjs/common';
import {
DeepPartial,
DeleteResult,
EntityManager,
EntityTarget,
InsertResult,
Repository,
UpdateResult,
} from 'typeorm';
import { InjectEntityManager } from '@nestjs/typeorm';
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';

@Injectable()
export class TypeormService {
constructor(
@InjectEntityManager() private readonly entityManager: EntityManager,
) {}

// Get repository for a specific entity
private getRepository<T>(entity: EntityTarget<T>): Repository<T> {
return this.entityManager.getRepository(entity);
}

// Find all records with optional conditions
async find<T>(entity: EntityTarget<T>, options?: object): Promise<T[]> {
return await this.getRepository(entity).find(options);
}

// Find one record by conditions
async findOne<T>(entity: EntityTarget<T>, conditions: object): Promise<T> {
const record = await this.getRepository(entity).findOne(conditions);
return record;
}

// Save a new entity or update existing
async save<T>(entity: EntityTarget<T>, data: Partial<T>): Promise<T> {
return await this.getRepository(entity).save(data as DeepPartial<T>);
}

// Update an existing entity by ID
async update<T>(
entity: EntityTarget<T>,
criteria: any,
partialEntity: QueryDeepPartialEntity<T>,
): Promise<UpdateResult> {
const repository = this.getRepository(entity);
return await repository.update(criteria, partialEntity);
}

// Delete an entity by ID
async delete<T>(
entity: EntityTarget<T>,
id: string | string[] | object,
): Promise<DeleteResult> {
const repository = this.getRepository(entity);
return repository.delete(id);
}

// Execute a raw query
async query<T>(query: string, parameters?: any[]): Promise<any> {
return await this.entityManager.query(query, parameters);
}

async queryWithBuilder<T>(
entity: EntityTarget<T>,
alias: string,
callback: (qb: any) => any,
): Promise<T[]> {
const repository = this.getRepository(entity);

// Create a query builder
const queryBuilder = repository.createQueryBuilder(alias);

// Apply custom query modifications using the callback
callback(queryBuilder);

// Execute and return results
return await queryBuilder.getMany();
}

async insert<T>(
entity: EntityTarget<T>,
values: QueryDeepPartialEntity<T>[],
returningFields: string[] = [],
): Promise<InsertResult> {
const repository = this.getRepository(entity);

const queryBuilder = repository
.createQueryBuilder()
.insert()
.into(entity)
.values(values);

if (returningFields.length) {
queryBuilder.returning(returningFields);
}

return await queryBuilder.execute();
}
}
10 changes: 9 additions & 1 deletion src/common/utils/constants.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ export const ERROR_MESSAGES = {
USERID_INVALID: 'Invalid UserId',
USERID_REQUIRED: 'UserId Required',
PROVIDE_ONE_USERID_IN_QUERY: 'Please provide userId in query params',
ENVIRONMENT_VARIABLES_MISSING: 'Environment variables missing!',
USERS_NOT_FOUND_IN_SERVICE: 'Users not found in user service',
SERVICE_NOT_FOUND: 'Service not found',
NO_PARTICIPANTS_FOUND: 'No participants found for the meeting',
MEETING_NOT_FOUND: 'Meeting not found',
NO_USERS_FOUND: 'No users found in system',
EVENT_DOES_NOT_EXIST: 'Event does not exist',
API_REQ_FAILURE: (url: string) => `Error occurred on API Request: ${url}`,
DB_QUERY_FAILURE: (url: string) => `Database Query Failed on API: ${url}`,
API_FAILURE: (url: string) => `API Failure: ${url}`,
Expand All @@ -111,6 +118,7 @@ export const SUCCESS_MESSAGES = {
EVENT_CREATED_LOG: (url: string) => `Event created with ID: ${url}`,
EVENTS_FETCHED_LOG: 'Successfully fetched events',
EVENT_UPDATED_LOG: 'Successfully updated events',
ATTENDANCE_MARKED_FOR_MEETING: 'Attendance marked for meeting',
};

export const API_ID = {
Expand All @@ -129,5 +137,5 @@ export const API_ID = {
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',
MARK_ZOOM_ATTENDANCE: 'mark.zoom.event.attendance',
MARK_EVENT_ATTENDANCE: 'api.event.mark.attendance',
};
2 changes: 1 addition & 1 deletion src/common/utils/transformer/date.transformer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BadRequestException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { getTimezoneDateString } from 'src/common/utils/pipe.util';
import { getTimezoneDateString } from '../../../common/utils/pipe.util';
import { ValueTransformer } from 'typeorm';

export class TimeZoneTransformer implements ValueTransformer {
Expand Down
47 changes: 47 additions & 0 deletions src/common/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,50 @@ export type RecurrencePattern = {
value: string;
};
};

export type ZoomParticipant = {
id: string;
user_id: string;
name: string;
user_email: string;
join_time: string;
leave_time: string;
duration: number;
registrant_id: string;
failover: boolean;
status: string;
groupId: string;
internal_user: boolean;
};

export type UserDetails = {
userId: string;
username: string;
email: string;
name: string;
role: string;
mobile: string;
createdBy: string;
updatedBy: string;
createdAt: string;
updatedAt: string;
status: string;
total_count: string;
};

export interface AttendanceRecord {
userId: string;
attendance: 'present' | 'absent';
metaData: {
duration: number;
joinTime: string;
leaveTime: string;
};
}

export interface InZoomMeetingUserDetails {
user_email: string;
duration: number;
join_time: string;
leave_time: string;
}
28 changes: 19 additions & 9 deletions src/modules/attendance/attendance.controller.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,43 @@
import { Body, Controller, Post, Res, Req, UseFilters } from '@nestjs/common';
import {
Body,
Controller,
Post,
Res,
Req,
UseFilters,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { Response, Request } from 'express';
import { ApiBody, ApiQuery, ApiTags } from '@nestjs/swagger';
import { AttendanceService } from './attendance.service';
import { AllExceptionsFilter } from 'src/common/filters/exception.filter';
import { MarkZoomAttendanceDto } from './dto/MarkZoomAttendance.dto';
import { checkValidUserId } from 'src/common/utils/functions.util';
import { API_ID, ERROR_MESSAGES } from 'src/common/utils/constants.util';
import { AllExceptionsFilter } from '../../common/filters/exception.filter';
import { MarkMeetingAttendanceDto } from './dto/MarkAttendance.dto';
import { checkValidUserId } from '../../common/utils/functions.util';
import { API_ID, ERROR_MESSAGES } from '../../common/utils/constants.util';

@Controller('attendance/v1')
@ApiTags('Event-Attendance')
export class EventAttendance {
constructor(private readonly attendanceService: AttendanceService) {}

@UseFilters(new AllExceptionsFilter(API_ID.MARK_ZOOM_ATTENDANCE))
@UseFilters(new AllExceptionsFilter(API_ID.MARK_EVENT_ATTENDANCE))
@Post('/markeventattendance')
@ApiBody({ type: MarkZoomAttendanceDto })
@ApiBody({ type: MarkMeetingAttendanceDto })
@ApiQuery({
name: 'userId',
required: true,
description: ERROR_MESSAGES.USERID_REQUIRED,
example: '123e4567-e89b-12d3-a456-426614174000',
})
@UsePipes(new ValidationPipe({ transform: true }))
async markEventAttendance(
@Body() markZoomAttendanceDto: MarkZoomAttendanceDto,
@Body() markZoomAttendanceDto: MarkMeetingAttendanceDto,
@Res() response: Response,
@Req() request: Request,
): Promise<Response> {
const userId: string = checkValidUserId(request.query?.userId);
return this.attendanceService.markAttendanceForZoomMeetingParticipants(
return this.attendanceService.markAttendanceForMeetingParticipants(
markZoomAttendanceDto,
userId,
response,
Expand Down
15 changes: 13 additions & 2 deletions src/modules/attendance/attendance.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,21 @@ import { EventAttendance } from './attendance.controller';
import { AttendanceService } from './attendance.service';
import { HttpModule } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config';
import { OnlineMeetingAdapter } from '../../online-meeting-adapters/onlineMeeting.adapter';
import { ZoomService } from '../../online-meeting-adapters/zoom/zoom.adapter';
import { TypeOrmModule } from '@nestjs/typeorm';
import { EventRepetition } from '../event/entities/eventRepetition.entity';
import { TypeormService } from 'src/common/services/typeorm.service';

@Module({
imports: [HttpModule],
imports: [HttpModule, TypeOrmModule.forFeature([EventRepetition])],
controllers: [EventAttendance],
providers: [AttendanceService, ConfigService],
providers: [
AttendanceService,
ConfigService,
OnlineMeetingAdapter,
ZoomService,
TypeormService,
],
})
export class AttendanceModule {}
Loading