diff --git a/.github/workflows/eks-pratham-deployment.yaml b/.github/workflows/eks-pratham-deployment.yaml index 1fd3dbe..a71a3a8 100644 --- a/.github/workflows/eks-pratham-deployment.yaml +++ b/.github/workflows/eks-pratham-deployment.yaml @@ -3,7 +3,6 @@ on: push: branches: - main - env: ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }} EKS_CLUSTER_NAME: ${{ secrets.EKS_CLUSTER_NAME }} @@ -13,77 +12,77 @@ jobs: name: Deployment runs-on: ubuntu-latest steps: - - name: Set short git commit SHA - id: commit - uses: prompt/actions-commit-hash@v2 - - name: Check out code - uses: actions/checkout@v2 - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{env.AWS_REGION}} - - name: Setup Node Env - uses: actions/setup-node@v3 - with: - node-version: 21.1.0 - - name: Copy .env file - env: - ENV_FILE_CONTENT: '${{ secrets.ENV_FILE_CONTENT }}"' - run: echo "$ENV_FILE_CONTENT" > manifest/configmap.yaml - - name: Show PWD and list content - run: | - echo "Current Working Directory: pwd" - pwd - ls -ltra - - name: Creating Dockerfile - env: - DOCKERFILE_FILE_CONTENT: ${{ secrets.DOCKERFILE_FILE_CONTENT }} - run: echo "$DOCKERFILE_FILE_CONTENT" > Dockerfile - - name: Show PWD and list content - run: | + - name: Set short git commit SHA + id: commit + uses: prompt/actions-commit-hash@v2 + - name: Check out code + uses: actions/checkout@v2 + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{env.AWS_REGION}} + - name: Setup Node Env + uses: actions/setup-node@v3 + with: + node-version: 21.1.0 + - name: Copy .env file + env: + ENV_FILE_CONTENT: '${{ secrets.ENV_FILE_CONTENT }}"' + run: echo "$ENV_FILE_CONTENT" > manifest/configmap.yaml + - name: Show PWD and list content + run: | echo "Current Working Directory: pwd" pwd ls -ltra - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ env.AWS_REGION }} - - name: Login to Amazon ECR - id: login-ecr - uses: aws-actions/amazon-ecr-login@v1 - - name: Build, tag, and push image to Amazon ECR - env: - ECR_REGISTRY: ${{ secrets.ECR_REPOSITORY }} - IMAGE_TAG: ${{ secrets.ECR_IMAGE }} - run: | - docker build -t ${{ secrets.ECR_REPOSITORY }}:${{ secrets.IMAGE_TAG }} . - docker push ${{ secrets.ECR_REPOSITORY }}:${{ secrets.IMAGE_TAG }} - - name: Update kube config - run: aws eks update-kubeconfig --name ${{ secrets.EKS_CLUSTER_NAME }} --region ${{ secrets.AWS_REGION_NAME }} - - name: Deploy to EKS - env: - ECR_REGISTRY: ${{ secrets.ECR_REPOSITORY }} - IMAGE_TAG: ${{ secrets.IMAGE_TAG }} - ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }} - ECR_IMAGE: ${{ secrets.ECR_IMAGE }} - run: | - export ECR_REPOSITORY=${{ secrets.ECR_REPOSITORY }} - export IMAGE_TAG=${{ secrets.IMAGE_TAG }} - export ECR_IMAGE=${{ secrets.ECR_IMAGE }} - envsubst < manifest/event-service.yaml > manifest/event-service-updated.yaml - cat manifest/event-service-updated.yaml - rm -rf manifest/event-service.yaml - kubectl delete deployment eventmanagement - kubectl delete svc eventmanagement - kubectl delete cm event-service-config - kubectl apply -f manifest/event-service-updated.yaml - kubectl apply -f manifest/configmap.yaml - sleep 10 - kubectl get pods - kubectl get services - kubectl get deployment - kubectl apply -f manifest/ + - name: Creating Dockerfile + env: + DOCKERFILE_FILE_CONTENT: ${{ secrets.DOCKERFILE_FILE_CONTENT }} + run: echo "$DOCKERFILE_FILE_CONTENT" > Dockerfile + - name: Show PWD and list content + run: | + echo "Current Working Directory: pwd" + pwd + ls -ltra + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ secrets.ECR_REPOSITORY }} + IMAGE_TAG: ${{ secrets.ECR_IMAGE }} + run: | + docker build -t ${{ secrets.ECR_REPOSITORY }}:${{ secrets.IMAGE_TAG }} . + docker push ${{ secrets.ECR_REPOSITORY }}:${{ secrets.IMAGE_TAG }} + - name: Update kube config + run: aws eks update-kubeconfig --name ${{ secrets.EKS_CLUSTER_NAME }} --region ${{ secrets.AWS_REGION_NAME }} + - name: Deploy to EKS + env: + ECR_REGISTRY: ${{ secrets.ECR_REPOSITORY }} + IMAGE_TAG: ${{ secrets.IMAGE_TAG }} + ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }} + ECR_IMAGE: ${{ secrets.ECR_IMAGE }} + run: | + export ECR_REPOSITORY=${{ secrets.ECR_REPOSITORY }} + export IMAGE_TAG=${{ secrets.IMAGE_TAG }} + export ECR_IMAGE=${{ secrets.ECR_IMAGE }} + envsubst < manifest/event-service.yaml > manifest/event-service-updated.yaml + cat manifest/event-service-updated.yaml + rm -rf manifest/event-service.yaml + kubectl delete deployment eventmanagement + kubectl delete svc eventmanagement + kubectl delete cm event-service-config + kubectl apply -f manifest/event-service-updated.yaml + kubectl apply -f manifest/configmap.yaml + sleep 10 + kubectl get pods + kubectl get services + kubectl get deployment + kubectl apply -f manifest/ diff --git a/package.json b/package.json index 92b75a6..73f35b8 100644 --- a/package.json +++ b/package.json @@ -31,9 +31,11 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "moment-timezone": "^0.5.45", + "nest-winston": "^1.9.7", "pg": "^8.13.0", "reflect-metadata": "^0.1.13", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "winston": "^3.16.0" }, "devDependencies": { "@nestjs/cli": "^10.0.0", diff --git a/src/common/filters/exception.filter.ts b/src/common/filters/exception.filter.ts index 2003a17..c4699d1 100644 --- a/src/common/filters/exception.filter.ts +++ b/src/common/filters/exception.filter.ts @@ -6,9 +6,10 @@ import { HttpStatus, } from '@nestjs/common'; import { QueryFailedError } from 'typeorm'; -import { Response } from 'express'; +import { Request, Response } from 'express'; import APIResponse from '../utils/response'; import { ERROR_MESSAGES } from '../utils/constants.util'; +import { LoggerWinston } from '../logger/logger.util'; @Catch() export class AllExceptionsFilter implements ExceptionFilter { @@ -20,6 +21,8 @@ export class AllExceptionsFilter implements ExceptionFilter { ) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); + const request = ctx.getRequest(); + const status = exception instanceof HttpException ? exception.getStatus() : 500; @@ -34,6 +37,13 @@ export class AllExceptionsFilter implements ExceptionFilter { ERROR_MESSAGES.BAD_REQUEST, statusCode.toString(), ); + LoggerWinston.error( + ERROR_MESSAGES.API_REQ_FAILURE(request.url), + errorMessage, + request.method, + typeof request.query === 'string' ? request.query : '', + ); + return response.status(statusCode).json(errorResponse); } else if (exception instanceof QueryFailedError) { const statusCode = HttpStatus.UNPROCESSABLE_ENTITY; @@ -43,10 +53,15 @@ export class AllExceptionsFilter implements ExceptionFilter { ERROR_MESSAGES.INTERNAL_SERVER_ERROR, statusCode.toString(), ); + LoggerWinston.error( + ERROR_MESSAGES.DB_QUERY_FAILURE(request.url), + (exception as QueryFailedError).message, + request.method, + typeof request.query === 'string' ? request.query : '', + ); return response.status(statusCode).json(errorResponse); } const detailedErrorMessage = `${errorMessage}`; - console.log('detailedErrorMessage', detailedErrorMessage); const errorResponse = APIResponse.error( this.apiId, detailedErrorMessage, @@ -55,6 +70,12 @@ export class AllExceptionsFilter implements ExceptionFilter { : ERROR_MESSAGES.INTERNAL_SERVER_ERROR, status.toString(), ); + LoggerWinston.error( + ERROR_MESSAGES.API_FAILURE(request.url), + errorResponse.result, + request.method, + typeof request.query === 'string' ? request.query : '', + ); return response.status(status).json(errorResponse); } } diff --git a/src/common/logger/logger.util.ts b/src/common/logger/logger.util.ts new file mode 100644 index 0000000..61c4b1b --- /dev/null +++ b/src/common/logger/logger.util.ts @@ -0,0 +1,79 @@ +import * as winston from 'winston'; + +export class LoggerWinston { + private static logger: winston.Logger; + + static getLogger() { + if (!this.logger) { + const customFormat = winston.format.printf( + ({ timestamp, level, message, context, user, error }) => { + return JSON.stringify({ + timestamp: timestamp, + context: context, + user: user, + level: level, + message: message, + error: error, + }); + }, + ); + + this.logger = winston.createLogger({ + level: 'info', + format: winston.format.combine( + winston.format.timestamp(), + customFormat, + ), + transports: [ + new winston.transports.File({ + filename: 'error.log', + level: 'error', + }), + new winston.transports.File({ filename: 'combined.log' }), + ], + }); + } + return this.logger; + } + static log(message: string, context?: string, user?: string) { + this.getLogger().log({ + level: 'info', + message: message, + context: context, + user: user, + timestamp: new Date().toISOString(), + }); + } + + static error( + message: string, + error?: string, + context?: string, + user?: string, + ) { + this.getLogger().error({ + level: 'error', + message: message, + error: error, + context: context, + user: user, + timestamp: new Date().toISOString(), + }); + } + + static warn(message: string, context?: string) { + this.getLogger().warn({ + message: message, + context: context, + timestamp: new Date().toISOString(), + }); + } + + static debug(message: string, context?: string) { + this.getLogger().debug({ + message: message, + context: context, + timestamp: new Date().toISOString(), + }); + } +} diff --git a/src/common/utils/constants.util.ts b/src/common/utils/constants.util.ts index 9f9381c..68d7b54 100644 --- a/src/common/utils/constants.util.ts +++ b/src/common/utils/constants.util.ts @@ -87,6 +87,11 @@ export const ERROR_MESSAGES = { END_CONDITION_BY_OCCURENCES: 'End condition by occurrences is not implemented yet', EVENT_TYPE_CHANGE_NOT_SUPPORTED: 'Event type change not supported', + USERID_INVALID: 'Invalid UserId', + PROVIDE_ONE_USERID_IN_QUERY: 'Please provide only one userid in query', + 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}`, }; export const SUCCESS_MESSAGES = { @@ -102,6 +107,9 @@ export const SUCCESS_MESSAGES = { 'Event attendee history item updated successfully', EVENT_ATTENDEE_HISTORY_ITEM_DELETED: 'Event attendee history item deleted successfully', + EVENT_CREATED_LOG: (url: string) => `Event created with ID: ${url}`, + EVENTS_FETCHED_LOG: 'Successfully fetched events', + EVENT_UPDATED_LOG: 'Successfully updated events', }; export const API_ID = { diff --git a/src/common/utils/functions.util.ts b/src/common/utils/functions.util.ts index 709ee5d..eb55a1f 100644 --- a/src/common/utils/functions.util.ts +++ b/src/common/utils/functions.util.ts @@ -1,3 +1,7 @@ +import { BadRequestException } from '@nestjs/common'; +import { isUUID } from 'class-validator'; +import { ERROR_MESSAGES } from './constants.util'; + export const compareArrays = (a: number[], b: number[]): boolean => { if (a.length !== b.length) { return false; @@ -22,3 +26,13 @@ export const getNextDay = (currentDate: Date): Date => { return nextDay; }; + +export const checkValidUserId = (userId: any): string => { + if (typeof userId !== 'string') { + throw new BadRequestException(ERROR_MESSAGES.PROVIDE_ONE_USERID_IN_QUERY); + } + if (!userId || !isUUID(userId)) { + throw new BadRequestException(ERROR_MESSAGES.USERID_INVALID); + } + return userId; +}; diff --git a/src/modules/event/dto/create-event.dto.ts b/src/modules/event/dto/create-event.dto.ts index 1e7853b..87efe58 100644 --- a/src/modules/event/dto/create-event.dto.ts +++ b/src/modules/event/dto/create-event.dto.ts @@ -347,18 +347,8 @@ export class CreateEventDto { @IsNotEmpty() status: string; - @ApiProperty({ - type: String, - description: 'createdBy', - example: 'eff008a8-2573-466d-b877-fddf6a4fc13e', - }) createdBy: string; - @ApiProperty({ - type: String, - description: 'updatedBy', - example: 'eff008a8-2573-466d-b877-fddf6a4fc13e', - }) updatedBy: string; @ApiProperty({ diff --git a/src/modules/event/dto/update-event.dto.ts b/src/modules/event/dto/update-event.dto.ts index 6a6cb6f..21c5210 100644 --- a/src/modules/event/dto/update-event.dto.ts +++ b/src/modules/event/dto/update-event.dto.ts @@ -187,17 +187,6 @@ export class UpdateEventDto { @IsIn(['Zoom', 'GoogleMeet']) onlineProvider: string; - // @IsString() - // @IsOptional() - // createdBy: string; - - @ApiProperty({ - type: String, - description: 'updatedBy', - example: 'eff008a8-2573-466d-b877-fddf6a4fc13e', - }) - @IsString() - // @IsOptional() updatedBy: string; @IsOptional() diff --git a/src/modules/event/event.controller.ts b/src/modules/event/event.controller.ts index b2e9ea2..6dcb84e 100644 --- a/src/modules/event/event.controller.ts +++ b/src/modules/event/event.controller.ts @@ -6,6 +6,7 @@ import { Param, UsePipes, Res, + Req, ValidationPipe, BadRequestException, UseFilters, @@ -21,8 +22,9 @@ import { ApiOkResponse, ApiResponse, ApiTags, + ApiQuery, } from '@nestjs/swagger'; -import { Response } from 'express'; +import { Request, Response } from 'express'; import { SearchFilterDto } from './dto/search-event.dto'; import { DateValidationPipe, @@ -38,6 +40,7 @@ import { ERROR_MESSAGES, SUCCESS_MESSAGES, } from 'src/common/utils/constants.util'; +import { checkValidUserId } from 'src/common/utils/functions.util'; @Controller('event/v1') @ApiTags('Create Event') @@ -50,6 +53,7 @@ export class EventController { @UseFilters(new AllExceptionsFilter(API_ID.CREATE_EVENT)) @Post('/create') @ApiBody({ type: CreateEventDto }) + @ApiQuery({ name: 'userid', required: true }) @UsePipes( new ValidationPipe({ transform: true }), new DateValidationPipe(), @@ -67,13 +71,18 @@ export class EventController { async create( @Body() createEventDto: CreateEventDto, @Res() response: Response, + @Req() request: Request, ) { + const userId: string = checkValidUserId(request.query.userid); + createEventDto.createdBy = userId; + createEventDto.updatedBy = userId; return this.eventService.createEvent(createEventDto, response); } @UseFilters(new AllExceptionsFilter(API_ID.GET_EVENTS)) @Post('/list') @ApiBody({ type: SearchFilterDto }) + @ApiQuery({ name: 'userid', required: true }) @ApiInternalServerErrorResponse({ description: ERROR_MESSAGES.INTERNAL_SERVER_ERROR, }) @@ -86,15 +95,18 @@ export class EventController { status: 200, }) async findAll( - @Res() response: Response, @Body() requestBody: SearchFilterDto, + @Res() response: Response, + @Req() request: Request, ) { - return this.eventService.getEvents(response, requestBody); + const userId: string = checkValidUserId(request.query.userid); + return this.eventService.getEvents(response, requestBody, userId); } @UseFilters(new AllExceptionsFilter(API_ID.UPDATE_EVENT)) @Patch('/:id') @ApiBody({ type: UpdateEventDto }) + @ApiQuery({ name: 'userid', required: true }) @ApiResponse({ status: 200, description: SUCCESS_MESSAGES.EVENT_UPDATED }) @ApiInternalServerErrorResponse({ description: ERROR_MESSAGES.INTERNAL_SERVER_ERROR, @@ -104,10 +116,13 @@ export class EventController { @Body(new ValidationPipe({ transform: true })) updateEventDto: UpdateEventDto, @Res() response: Response, + @Req() request: Request, ) { if (!updateEventDto || Object.keys(updateEventDto).length === 0) { throw new BadRequestException(ERROR_MESSAGES.INVALID_REQUEST_BODY); } + const userId: string = checkValidUserId(request.query.userid); + updateEventDto.updatedBy = userId; return this.eventService.updateEvent(id, updateEventDto, response); } } diff --git a/src/modules/event/event.service.ts b/src/modules/event/event.service.ts index 4e48b29..c31b5ad 100644 --- a/src/modules/event/event.service.ts +++ b/src/modules/event/event.service.ts @@ -24,7 +24,11 @@ import { Response } from 'express'; import APIResponse from 'src/common/utils/response'; import { AttendeesService } from '../attendees/attendees.service'; import { EventDetail } from './entities/eventDetail.entity'; -import { API_ID, ERROR_MESSAGES } from 'src/common/utils/constants.util'; +import { + API_ID, + ERROR_MESSAGES, + SUCCESS_MESSAGES, +} from 'src/common/utils/constants.util'; import { EventRepetition } from './entities/eventRepetition.entity'; import { DaysOfWeek, @@ -41,6 +45,8 @@ import { } from 'src/common/pipes/event-validation.pipe'; import { compareArrays } from 'src/common/utils/functions.util'; import * as moment from 'moment-timezone'; +import { LoggerWinston } from 'src/common/logger/logger.util'; + @Injectable() export class EventService { private readonly eventCreationLimit: number; @@ -104,12 +110,18 @@ export class EventService { // } } + LoggerWinston.log( + SUCCESS_MESSAGES.EVENT_CREATED_LOG(createdEvent.res?.eventId), + apiId, + createEventDto.createdBy, + ); + return response .status(HttpStatus.CREATED) .json(APIResponse.success(apiId, createdEvent.res, 'Created')); } - async getEvents(response, requestBody) { + async getEvents(response, requestBody, userId: string) { const apiId = API_ID.GET_EVENTS; this.validateTimezone(); @@ -164,6 +176,7 @@ export class EventService { if (finalResult.length === 0) { throw new NotFoundException(ERROR_MESSAGES.EVENT_NOT_FOUND); } + LoggerWinston.log(SUCCESS_MESSAGES.EVENTS_FETCHED_LOG, apiId, userId); return response .status(HttpStatus.OK) .json( @@ -310,6 +323,12 @@ export class EventService { eventRepetition, ); } + + LoggerWinston.log( + SUCCESS_MESSAGES.EVENT_UPDATED_LOG, + apiId, + updateBody.updatedBy, + ); return response .status(HttpStatus.OK) .json(APIResponse.success(apiId, result, 'OK'));