Skip to content

Commit

Permalink
Merge pull request #8390 from ever-co/feat/#8386-global-logging-api
Browse files Browse the repository at this point in the history
[Feat] #8386 Global Logging For API Request (DB Structure)
  • Loading branch information
rahul-rocket authored Oct 11, 2024
2 parents 882dde0 + f96b6f7 commit 1ce997e
Show file tree
Hide file tree
Showing 20 changed files with 696 additions and 12 deletions.
5 changes: 1 addition & 4 deletions packages/contracts/src/activity-log.model.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { ActorTypeEnum, IBasePerTenantAndOrganizationEntityModel, ID } from './base-entity.model';
import { ActorTypeEnum, IBasePerTenantAndOrganizationEntityModel, ID, JsonData } from './base-entity.model';
import { IUser } from './user.model';

// Define a type for JSON data
export type JsonData = Record<string, any> | string;

/**
* Interface representing an activity log entry.
*/
Expand Down
35 changes: 35 additions & 0 deletions packages/contracts/src/api-call-log.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { IBasePerTenantAndOrganizationEntityModel, ID, JsonData } from './base-entity.model';
import { IRelationalUser } from './user.model';

/**
* Enum representing the HTTP method used in the request.
*/
export enum RequestMethod {
GET = 0,
POST = 1,
PUT = 2,
DELETE = 3,
PATCH = 4,
ALL = 5,
OPTIONS = 6,
HEAD = 7,
SEARCH = 8
}

/**
* Interface representing an API call log entry.
*/
export interface IApiCallLog extends IBasePerTenantAndOrganizationEntityModel, IRelationalUser {
correlationId: ID; // Correlation ID to track the request across services.
url: string; // The request URL that was called.
method: RequestMethod; // The HTTP method (GET, POST, etc.) used in the request.
statusCode: number; // The HTTP status code returned from the request.
requestHeaders: JsonData; // Request headers stored as JSON string.
requestBody: JsonData; // Request body stored as JSON string.
responseBody: JsonData; // Response body stored as JSON string.
requestTime: Date; // The timestamp when the request was initiated.
responseTime: Date; // The timestamp when the response was completed.
ipAddress: string; // The IP address of the client making the request.
protocol: string; // The protocol used in the request (HTTP, HTTPS).
userAgent: string; // User-Agent string of the client making the request (could be a browser, desktop app, Postman, etc.).
}
3 changes: 3 additions & 0 deletions packages/contracts/src/base-entity.model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { ITenant } from './tenant.model';
import { IOrganization } from './organization.model';

// Define a type for JSON data
export type JsonData = Record<string, any> | string;

/**
* @description
* An entity ID. Represents a unique identifier as a string.
Expand Down
7 changes: 5 additions & 2 deletions packages/contracts/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './accounting-template.model';
/** App Setting Model */
export * from './activity-log.model';
export * from './api-call-log.model';
export * from './app.model';
export * from './appointment-employees.model';
export * from './approval-policy.model';
Expand Down Expand Up @@ -135,13 +136,15 @@ export * from './user.model';
export * from './wakatime.model';
export * from './activity-watch.model';

export { IBaseEntityModel as BaseEntityModel, ID } from './base-entity.model';
export {
IBaseEntityModel as BaseEntityModel,
ID,
IBasePerTenantAndOrganizationEntityModel,
IBasePerTenantEntityModel,
IBaseSoftDeleteEntityModel,
IBaseRelationsEntityModel,
ActorTypeEnum
ActorTypeEnum,
JsonData
} from './base-entity.model';

export * from './proxy.model';
3 changes: 1 addition & 2 deletions packages/contracts/src/organization-sprint.model.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { IOrganizationProjectModule } from './organization-project-module.model';
import { IBasePerTenantAndOrganizationEntityModel, ID } from './base-entity.model';
import { IBasePerTenantAndOrganizationEntityModel, ID, JsonData } from './base-entity.model';
import { IOrganizationProject } from './organization-projects.model';
import { ITask } from './task.model';
import { IEmployeeEntityInput, IMemberEntityBased } from './employee.model';
import { IRelationalRole } from './role.model';
import { JsonData } from './activity-log.model';
import { IUser } from './user.model';

// Base interface with optional properties
Expand Down
3 changes: 1 addition & 2 deletions packages/contracts/src/task-view.model.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { IBasePerTenantAndOrganizationEntityModel } from './base-entity.model';
import { IBasePerTenantAndOrganizationEntityModel, JsonData } from './base-entity.model';
import { IRelationalOrganizationTeam } from './organization-team.model';
import { IRelationalOrganizationProject } from './organization-projects.model';
import { IRelationalOrganizationProjectModule } from './organization-project-module.model';
import { IRelationalOrganizationSprint } from './organization-sprint.model';
import { JsonData } from './activity-log.model';

export interface ITaskViewBase
extends IBasePerTenantAndOrganizationEntityModel,
Expand Down
4 changes: 2 additions & 2 deletions packages/contracts/src/user.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export interface IFindMeUser extends IBaseRelationsEntityModel {
}

export interface IRelationalUser {
user?: IUser;
userId?: IUser['id'];
user?: IUser; // User who performed the action (if applicable).
userId?: ID; // The ID of the user who performed the action (if applicable).
}

export interface IUser extends IBasePerTenantEntityModel, IRelationalImageAsset {
Expand Down
31 changes: 31 additions & 0 deletions packages/core/src/api-call-log/api-call-log.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
import { ApiOperation, ApiResponse } from '@nestjs/swagger';
import { Permissions } from '../shared/decorators';
import { PermissionGuard, TenantPermissionGuard } from '../shared/guards';
import { IApiCallLog, IPagination } from '@gauzy/contracts';
import { ApiCallLogService } from './api-call-log.service';
import { ApiCallLogFilterDTO } from './dto/api-call-log-filter.dto';

@UseGuards(TenantPermissionGuard, PermissionGuard)
@Permissions()
@Controller('/api-call-log')
export class ApiCallLogController {
constructor(private readonly _apiCallLogService: ApiCallLogService) {}

/**
* Retrieves a paginated and filtered list of all API call logs from the system.
*
* @param filters DTO containing filtering options like `organizationId`, `correlationId`, `url`, etc.
* @returns A promise that resolves to a paginated list of `IApiCallLog` objects.
*/
@ApiOperation({ summary: 'Get all API call logs with mandatory organizationId and optional filters' })
@ApiResponse({
status: 200,
description: 'Returns a list of all API call logs with filters applied.'
})
@ApiResponse({ status: 500, description: 'Internal server error.' })
@Get('/')
async findAll(@Query() filters: ApiCallLogFilterDTO): Promise<IPagination<IApiCallLog>> {
return this._apiCallLogService.findAllLogs(filters);
}
}
154 changes: 154 additions & 0 deletions packages/core/src/api-call-log/api-call-log.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { EntityRepositoryType } from '@mikro-orm/core';
import { JoinColumn, RelationId } from 'typeorm';
import { IsEnum, IsNotEmpty, IsOptional, IsUUID } from 'class-validator';
import { isMySQL, isPostgres } from '@gauzy/config';
import { IApiCallLog, ID, IUser, JsonData, RequestMethod } from '@gauzy/contracts';
import { ColumnIndex, MultiORMColumn, MultiORMEntity, MultiORMManyToOne } from '../core/decorators/entity';
import { TenantOrganizationBaseEntity, User } from '../core/entities/internal';
import { MikroOrmApiCallLogRepository } from './repository/mikro-orm-api-call-log.repository';

@MultiORMEntity('api_call_log', { mikroOrmRepository: () => MikroOrmApiCallLogRepository })
export class ApiCallLog extends TenantOrganizationBaseEntity implements IApiCallLog {
[EntityRepositoryType]?: MikroOrmApiCallLogRepository;

/**
* Correlation ID to track the request across services
*/
@ApiProperty({ type: () => String })
@IsNotEmpty()
@IsUUID()
@ColumnIndex()
@MultiORMColumn()
correlationId: ID;

/**
* The request URL that was called
*/
@ApiProperty({ type: () => String })
@IsNotEmpty()
@ColumnIndex()
@MultiORMColumn()
url: string;

/**
* The HTTP method (GET, POST, etc.) used in the request
*/
@ApiProperty({ enum: RequestMethod })
@IsNotEmpty()
@IsEnum(RequestMethod)
@ColumnIndex()
@MultiORMColumn()
method: RequestMethod;

/**
* Request headers stored as JSON string
*/
@ApiProperty({ type: () => String })
@IsNotEmpty()
@MultiORMColumn({ type: isPostgres() ? 'jsonb' : isMySQL() ? 'json' : 'text' })
requestHeaders: JsonData;

/**
* Request body stored as JSON string
*/
@ApiProperty({ type: () => String })
@IsNotEmpty()
@MultiORMColumn({ type: isPostgres() ? 'jsonb' : isMySQL() ? 'json' : 'text' })
requestBody: JsonData;

/**
* Response body stored as JSON string
*/
@ApiProperty({ type: () => String })
@IsNotEmpty()
@MultiORMColumn({ type: isPostgres() ? 'jsonb' : isMySQL() ? 'json' : 'text' })
responseBody: JsonData;

/**
* The HTTP status code returned from the request
*/
@ApiProperty({ type: () => Number })
@IsNotEmpty()
@ColumnIndex()
@MultiORMColumn()
statusCode: number;

/**
* The timestamp when the request was initiated
*/
@ApiProperty({ type: () => Date })
@IsNotEmpty()
@ColumnIndex()
@MultiORMColumn()
requestTime: Date;

/**
* The timestamp when the response was completed
*/
@ApiProperty({ type: () => Date })
@IsNotEmpty()
@ColumnIndex()
@MultiORMColumn()
responseTime: Date;

/**
* IP Address of the client making the request
*/
@ApiPropertyOptional({ type: () => String })
@IsOptional()
@ColumnIndex()
@MultiORMColumn({ nullable: true })
ipAddress: string;

/**
* The protocol used in the request (HTTP, HTTPS)
*/
@ApiProperty({ type: () => String })
@IsNotEmpty()
@ColumnIndex()
@MultiORMColumn()
protocol: string;

/**
* User-Agent string of the client making the request.
* This could be a browser, desktop app, Postman, or any other API client.
*/
@ApiProperty({ type: () => String })
@IsNotEmpty()
@MultiORMColumn()
userAgent: string;

/*
|--------------------------------------------------------------------------
| @ManyToOne
|--------------------------------------------------------------------------
*/

/**
* User who performed the action, if applicable.
*
* This relationship is nullable and uses the User entity.
*/
@MultiORMManyToOne(() => User, {
/** Indicates if the relation column value can be nullable or not. */
nullable: true,

/** Database cascade action on delete. */
onDelete: 'CASCADE'
})
@JoinColumn()
user?: IUser;

/**
* The ID of the user who performed the action.
* This column stores the user ID as a foreign key, if applicable.
*/
@ApiPropertyOptional({ type: () => String })
@IsOptional()
@IsUUID()
@RelationId((it: ApiCallLog) => it.user)
@ColumnIndex()
@MultiORMColumn({ nullable: true, relationId: true })
userId?: ID;
}
16 changes: 16 additions & 0 deletions packages/core/src/api-call-log/api-call-log.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Module } from '@nestjs/common';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { TypeOrmModule } from '@nestjs/typeorm';
import { RolePermissionModule } from '../role-permission/role-permission.module';
import { ApiCallLog } from './api-call-log.entity';
import { ApiCallLogService } from './api-call-log.service';
import { ApiCallLogController } from './api-call-log.controller';
import { TypeOrmApiCallLogRepository } from './repository/type-orm-api-call-log.repository';

@Module({
imports: [TypeOrmModule.forFeature([ApiCallLog]), MikroOrmModule.forFeature([ApiCallLog]), RolePermissionModule],
controllers: [ApiCallLogController],
providers: [ApiCallLogService, TypeOrmApiCallLogRepository],
exports: [ApiCallLogService]
})
export class ApiCallLogModule {}
Loading

0 comments on commit 1ce997e

Please sign in to comment.